mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Merge pull request #374 from meshtastic/2.2.1_Working_Changes
2.2.1 working changes
This commit is contained in:
commit
42a4057a4e
45 changed files with 1866 additions and 688 deletions
|
|
@ -7,6 +7,9 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
6DA39D8E2A92DC52007E311C /* MeshtasticAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */; };
|
||||
6DEDA55A2A957B8E00321D2E /* DetectionSensorLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */; };
|
||||
6DEDA55C2A9592F900321D2E /* MessageEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */; };
|
||||
C9697F9D279336B700250207 /* LocalMBTileOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */; };
|
||||
C9697FA527933B8C00250207 /* SQLite in Frameworks */ = {isa = PBXBuildFile; productRef = C9697FA427933B8C00250207 /* SQLite */; };
|
||||
DD0D3D222A55CEB10066DB71 /* CocoaMQTT in Frameworks */ = {isa = PBXBuildFile; productRef = DD0D3D212A55CEB10066DB71 /* CocoaMQTT */; };
|
||||
|
|
@ -115,6 +118,7 @@
|
|||
DDC2E18F26CE25FE0042C5E4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E18E26CE25FE0042C5E4 /* ContentView.swift */; };
|
||||
DDC2E1A726CEB3400042C5E4 /* LocationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E1A626CEB3400042C5E4 /* LocationHelper.swift */; };
|
||||
DDC3B274283F411B00AC321C /* LastHeardText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC3B273283F411B00AC321C /* LastHeardText.swift */; };
|
||||
DDC4C9FF2A8D982900CE201C /* DetectionSensorConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC4C9FE2A8D982900CE201C /* DetectionSensorConfig.swift */; };
|
||||
DDC4D568275499A500A4208E /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC4D567275499A500A4208E /* Persistence.swift */; };
|
||||
DDC94FC129CE063B0082EA6E /* BatteryLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC94FC029CE063B0082EA6E /* BatteryLevel.swift */; };
|
||||
DDC94FC229CE063B0082EA6E /* BatteryLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC94FC029CE063B0082EA6E /* BatteryLevel.swift */; };
|
||||
|
|
@ -194,6 +198,9 @@
|
|||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticAppDelegate.swift; sourceTree = "<group>"; };
|
||||
6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectionSensorLog.swift; sourceTree = "<group>"; };
|
||||
6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageEntityExtension.swift; sourceTree = "<group>"; };
|
||||
A65FA974296876BF00A97686 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
|
|
@ -318,6 +325,8 @@
|
|||
DDC2E18E26CE25FE0042C5E4 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
DDC2E1A626CEB3400042C5E4 /* LocationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationHelper.swift; sourceTree = "<group>"; };
|
||||
DDC3B273283F411B00AC321C /* LastHeardText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LastHeardText.swift; sourceTree = "<group>"; };
|
||||
DDC4C9FE2A8D982900CE201C /* DetectionSensorConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectionSensorConfig.swift; sourceTree = "<group>"; };
|
||||
DDC4CA012A8DAA3800CE201C /* MeshtasticDataModelV16.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV16.xcdatamodel; sourceTree = "<group>"; };
|
||||
DDC4D567275499A500A4208E /* Persistence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = "<group>"; };
|
||||
DDC94FC029CE063B0082EA6E /* BatteryLevel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryLevel.swift; sourceTree = "<group>"; };
|
||||
DDC94FC329CED7280082EA6E /* MeshtasticDataModelV10.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV10.xcdatamodel; sourceTree = "<group>"; };
|
||||
|
|
@ -428,6 +437,7 @@
|
|||
DD90860D26F69BAE00DC5189 /* NodeMap.swift */,
|
||||
DD73FD1028750779000852D6 /* PositionLog.swift */,
|
||||
DD14E72D2A82A614006E39BC /* RemoteHardware.swift */,
|
||||
6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */,
|
||||
);
|
||||
path = Nodes;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -517,6 +527,7 @@
|
|||
DDC94FCD29CF55310082EA6E /* RtttlConfig.swift */,
|
||||
DD6193782863875F00E59241 /* SerialConfig.swift */,
|
||||
DD415827285859C4009B0E59 /* TelemetryConfig.swift */,
|
||||
DDC4C9FE2A8D982900CE201C /* DetectionSensorConfig.swift */,
|
||||
);
|
||||
path = Module;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -621,6 +632,7 @@
|
|||
DDC2E15726CE248E0042C5E4 /* MeshtasticApp.swift */,
|
||||
DDC2E16526CE248F0042C5E4 /* Info.plist */,
|
||||
DDC2E15D26CE248F0042C5E4 /* Preview Content */,
|
||||
6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */,
|
||||
);
|
||||
path = Meshtastic;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -739,6 +751,7 @@
|
|||
DD964FC52975DBFD007C176F /* QueryCoreData.swift */,
|
||||
DD3CC6C128EB9D4900FA9159 /* UpdateCoreData.swift */,
|
||||
DD964FC1297272AE007C176F /* WaypointEntityExtension.swift */,
|
||||
6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */,
|
||||
);
|
||||
path = Persistence;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -1002,6 +1015,7 @@
|
|||
DD3501892852FC3B000FC853 /* Settings.swift in Sources */,
|
||||
DDDB443629F6287000EE2349 /* MapButtons.swift in Sources */,
|
||||
DD5D0A9C2931B9F200F7EA61 /* EthernetModes.swift in Sources */,
|
||||
6DEDA55A2A957B8E00321D2E /* DetectionSensorLog.swift in Sources */,
|
||||
DD5E5203298EE33B00D21B61 /* config.pb.swift in Sources */,
|
||||
DD798B072915928D005217CD /* ChannelMessageList.swift in Sources */,
|
||||
DDA6B2EB28420A7B003E8C16 /* NodeAnnotation.swift in Sources */,
|
||||
|
|
@ -1032,6 +1046,7 @@
|
|||
DD4A911E2708C65400501B7E /* AppSettings.swift in Sources */,
|
||||
DD5E5209298EE33B00D21B61 /* module_config.pb.swift in Sources */,
|
||||
DD2160AF28C5552500C17253 /* MQTTConfig.swift in Sources */,
|
||||
6DEDA55C2A9592F900321D2E /* MessageEntityExtension.swift in Sources */,
|
||||
DDDB444229F8A88700EE2349 /* Double.swift in Sources */,
|
||||
DD5E520F298EE33B00D21B61 /* cannedmessages.pb.swift in Sources */,
|
||||
DDB75A232A13CDA9006ED576 /* BatteryLevelCompact.swift in Sources */,
|
||||
|
|
@ -1054,6 +1069,7 @@
|
|||
DD5E520D298EE33B00D21B61 /* storeforward.pb.swift in Sources */,
|
||||
DD882F5D2772E4640005BF05 /* Contacts.swift in Sources */,
|
||||
DD964FC2297272AE007C176F /* WaypointEntityExtension.swift in Sources */,
|
||||
6DA39D8E2A92DC52007E311C /* MeshtasticAppDelegate.swift in Sources */,
|
||||
DD47E3CE26F103C600029299 /* NodeList.swift in Sources */,
|
||||
DD5E520A298EE33B00D21B61 /* channel.pb.swift in Sources */,
|
||||
DDDEE5E129DA3E1100A8E078 /* NodeInfoView.swift in Sources */,
|
||||
|
|
@ -1109,6 +1125,7 @@
|
|||
DDDB444E29F8AB0E00EE2349 /* Int.swift in Sources */,
|
||||
DD0F791B28713C8A00A6FDAD /* AdminMessageList.swift in Sources */,
|
||||
DD3CC6BC28E366DF00FA9159 /* Meshtastic.xcdatamodeld in Sources */,
|
||||
DDC4C9FF2A8D982900CE201C /* DetectionSensorConfig.swift in Sources */,
|
||||
DD5E5210298EE33B00D21B61 /* telemetry.pb.swift in Sources */,
|
||||
DD5E5205298EE33B00D21B61 /* mesh.pb.swift in Sources */,
|
||||
DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */,
|
||||
|
|
@ -1237,7 +1254,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.2;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
|
|
@ -1293,7 +1310,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.2;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
|
|
@ -1323,12 +1340,12 @@
|
|||
INFOPLIST_FILE = Meshtastic/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Meshtastic;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.2;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.0;
|
||||
MARKETING_VERSION = 2.2.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1357,12 +1374,12 @@
|
|||
INFOPLIST_FILE = Meshtastic/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Meshtastic;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.2;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.0;
|
||||
MARKETING_VERSION = 2.2.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1382,7 +1399,7 @@
|
|||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = GCH7VS5Y9R;
|
||||
INFOPLIST_FILE = MeshtasticTests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.2;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -1407,7 +1424,7 @@
|
|||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = GCH7VS5Y9R;
|
||||
INFOPLIST_FILE = MeshtasticTests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.2;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -1428,6 +1445,7 @@
|
|||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = GCH7VS5Y9R;
|
||||
INFOPLIST_FILE = MeshtasticUITests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.2;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -1448,6 +1466,7 @@
|
|||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = GCH7VS5Y9R;
|
||||
INFOPLIST_FILE = MeshtasticUITests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.2;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
|
@ -1468,6 +1487,7 @@
|
|||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CODE_SIGN_ENTITLEMENTS = Widgets/WidgetsExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = GCH7VS5Y9R;
|
||||
|
|
@ -1475,15 +1495,16 @@
|
|||
INFOPLIST_FILE = Widgets/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Widgets;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.2;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.0;
|
||||
MARKETING_VERSION = 2.2.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
|
|
@ -1499,6 +1520,7 @@
|
|||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CODE_SIGN_ENTITLEMENTS = Widgets/WidgetsExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = GCH7VS5Y9R;
|
||||
|
|
@ -1506,15 +1528,16 @@
|
|||
INFOPLIST_FILE = Widgets/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Widgets;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.2;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.0;
|
||||
MARKETING_VERSION = 2.2.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
|
|
@ -1622,6 +1645,7 @@
|
|||
DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
DDC4CA012A8DAA3800CE201C /* MeshtasticDataModelV16.xcdatamodel */,
|
||||
DD14E72C2A80738F006E39BC /* MeshtasticDataModelV15.xcdatamodel */,
|
||||
DD0E9C222A30CE3A00580CBB /* MeshtasticDataModelV14.xcdatamodel */,
|
||||
DDB75A1F2A10766D006ED576 /* MeshtasticDataModelV13.xcdatamodel */,
|
||||
|
|
@ -1638,7 +1662,7 @@
|
|||
DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */,
|
||||
DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */,
|
||||
);
|
||||
currentVersion = DD14E72C2A80738F006E39BC /* MeshtasticDataModelV15.xcdatamodel */;
|
||||
currentVersion = DDC4CA012A8DAA3800CE201C /* MeshtasticDataModelV16.xcdatamodel */;
|
||||
name = Meshtastic.xcdatamodeld;
|
||||
path = Meshtastic/Meshtastic.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
|
|
|
|||
|
|
@ -53,6 +53,21 @@ func telemetryToCsvFile(telemetry: [TelemetryEntity], metricsType: Int) -> Strin
|
|||
return csvString
|
||||
}
|
||||
|
||||
func detectionsToCsv(detections: [MessageEntity]) -> String {
|
||||
var csvString: String = ""
|
||||
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current)
|
||||
let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma").replacingOccurrences(of: ",", with: "")
|
||||
// Create Header
|
||||
csvString = "Detection event, \("timestamp".localized)"
|
||||
for d in detections {
|
||||
csvString += "\n"
|
||||
csvString += d.messagePayload ?? "Detection"
|
||||
csvString += ", "
|
||||
csvString += d.timestamp.formattedDate(format: dateFormatString).localized
|
||||
}
|
||||
return csvString
|
||||
}
|
||||
|
||||
func positionToCsvFile(positions: [PositionEntity]) -> String {
|
||||
var csvString: String = ""
|
||||
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current)
|
||||
|
|
|
|||
|
|
@ -10,45 +10,6 @@ import CocoaMQTT
|
|||
// ---------------------------------------------------------------------------------------
|
||||
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)
|
||||
|
|
@ -66,6 +27,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
@Published var invalidVersion = false
|
||||
@Published var isSwitchedOn: Bool = false
|
||||
@Published var automaticallyReconnect: Bool = true
|
||||
@Published var mqttProxyConnected: Bool = false
|
||||
public var minimumVersion = "2.0.0"
|
||||
public var connectedVersion: String
|
||||
public var isConnecting: Bool = false
|
||||
|
|
@ -78,6 +40,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
var lastPosition: CLLocationCoordinate2D?
|
||||
let emptyNodeNum: UInt32 = 4294967295
|
||||
let mqttManager = MqttClientProxyManager.shared
|
||||
var wantRangeTestPackets = false
|
||||
/* Meshtastic Service Details */
|
||||
var TORADIO_characteristic: CBCharacteristic!
|
||||
var FROMRADIO_characteristic: CBCharacteristic!
|
||||
|
|
@ -312,6 +275,45 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: MqttClientProxyManagerDelegate Methods
|
||||
func onMqttConnected() {
|
||||
mqttProxyConnected = true
|
||||
print("📲 Mqtt Client Proxy onMqttConnected now subscribing to \(mqttManager.topic).")
|
||||
mqttManager.mqttClientProxy?.subscribe(mqttManager.topic)
|
||||
}
|
||||
|
||||
func onMqttDisconnected() {
|
||||
mqttProxyConnected = false
|
||||
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) {
|
||||
mqttProxyConnected = false
|
||||
print("📲 Mqtt Client Proxy onMqttError: \(message)")
|
||||
}
|
||||
|
||||
// MARK: Protobuf Methods
|
||||
func requestDeviceMetadata(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32, context: NSManagedObjectContext) -> Int64 {
|
||||
|
||||
guard connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected else { return 0 }
|
||||
|
|
@ -503,7 +505,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
if decodedInfo.metadata.firmwareVersion.count > 0 && !invalidVersion {
|
||||
nowKnown = true
|
||||
deviceMetadataPacket(metadata: decodedInfo.metadata, fromNum: connectedPeripheral.num, context: context!)
|
||||
connectedPeripheral.firmwareVersion = decodedInfo.metadata.firmwareVersion ?? "unknown".localized
|
||||
connectedPeripheral.firmwareVersion = decodedInfo.metadata.firmwareVersion
|
||||
|
||||
let lastDotIndex = decodedInfo.metadata.firmwareVersion.lastIndex(of: ".")
|
||||
|
||||
|
|
@ -528,7 +530,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
// Log any other unknownApp calls
|
||||
if !nowKnown { MeshLogger.log("🕸️ MESH PACKET received for Unknown App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") }
|
||||
|
||||
case .textMessageApp:
|
||||
case .textMessageApp, .detectionSensorApp:
|
||||
textMessageAppPacket(packet: decodedInfo.packet, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!)
|
||||
case .remoteHardwareApp:
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Remote Hardware App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
|
|
@ -549,9 +551,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
case .serialApp:
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Serial App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
case .storeForwardApp:
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Store Forward App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
storeAndForwardPacket(packet: decodedInfo.packet, connectedNodeNum: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!)
|
||||
case .rangeTestApp:
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Range Test App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
if wantRangeTestPackets {
|
||||
textMessageAppPacket(packet: decodedInfo.packet, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!)
|
||||
}
|
||||
else {
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Range Test App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
}
|
||||
case .telemetryApp:
|
||||
if !invalidVersion { telemetryPacket(packet: decodedInfo.packet, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) }
|
||||
case .textMessageCompressedApp:
|
||||
|
|
@ -599,7 +606,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
peripherals.removeAll(where: { $0.peripheral.state == CBPeripheralState.disconnected })
|
||||
// Config conplete returns so we don't read the characteristic again
|
||||
|
||||
/// MQTT Client Proxy
|
||||
/// MQTT Client Proxy and RangeTest interest
|
||||
if connectedPeripheral.num > 0 {
|
||||
|
||||
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
|
||||
|
|
@ -607,12 +614,15 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
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])
|
||||
}
|
||||
}
|
||||
if fetchedNodeInfo.count == 1 && fetchedNodeInfo[0].rangeTestConfig?.enabled == true {
|
||||
wantRangeTestPackets = true;
|
||||
}
|
||||
|
||||
} catch {
|
||||
print("Failed to find a node info for the connected node")
|
||||
}
|
||||
|
|
@ -658,7 +668,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
self.startScanning()
|
||||
|
||||
// Try and connect to the preferredPeripherial first
|
||||
let preferredPeripheral = peripherals.filter({ $0.peripheral.identifier.uuidString == UserDefaults.preferredPeripheralId as? String ?? "" }).first
|
||||
let preferredPeripheral = peripherals.filter({ $0.peripheral.identifier.uuidString == UserDefaults.preferredPeripheralId as String }).first
|
||||
if preferredPeripheral != nil && preferredPeripheral?.peripheral != nil {
|
||||
connectTo(peripheral: preferredPeripheral!.peripheral)
|
||||
}
|
||||
|
|
@ -1533,6 +1543,32 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
return 0
|
||||
}
|
||||
|
||||
public func saveDetectionSensorModuleConfig(config: ModuleConfig.DetectionSensorConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 {
|
||||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.setModuleConfig.detectionSensor = config
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
meshPacket.channel = UInt32(adminIndex)
|
||||
meshPacket.priority = MeshPacket.Priority.reliable
|
||||
meshPacket.wantAck = true
|
||||
|
||||
var dataMessage = DataMessage()
|
||||
dataMessage.payload = try! adminPacket.serializedData()
|
||||
dataMessage.portnum = PortNum.adminApp
|
||||
meshPacket.decoded = dataMessage
|
||||
|
||||
let messageDescription = "Saved Telemetry Module Config for \(toUser.longName ?? "unknown".localized)"
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
|
||||
upsertDetectionSensorModuleConfigPacket(config: config, nodeNum: toUser.num, context: context!)
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
public func getChannel(channelIndex: UInt32, fromUser: UserEntity, toUser: UserEntity, wantResponse: Bool) -> Bool {
|
||||
|
||||
var adminPacket = AdminMessage()
|
||||
|
|
@ -1901,6 +1937,34 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
return false
|
||||
}
|
||||
|
||||
public func requestDetectionSensorModuleConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool {
|
||||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.detectionsensorConfig
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.priority = MeshPacket.Priority.reliable
|
||||
meshPacket.channel = UInt32(adminIndex)
|
||||
meshPacket.wantAck = true
|
||||
|
||||
var dataMessage = DataMessage()
|
||||
dataMessage.payload = try! adminPacket.serializedData()
|
||||
dataMessage.portnum = PortNum.adminApp
|
||||
dataMessage.wantResponse = true
|
||||
|
||||
meshPacket.decoded = dataMessage
|
||||
|
||||
let messageDescription = "🛎️ Requested Detection Sensor Module Config on admin channel \(adminIndex) for node: \(String(connectedPeripheral.num))"
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
public func requestSerialModuleConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool {
|
||||
|
||||
var adminPacket = AdminMessage()
|
||||
|
|
|
|||
|
|
@ -10,16 +10,16 @@ class LocalNotificationManager {
|
|||
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
|
||||
|
||||
if granted == true && error == nil {
|
||||
self.scheduleNotifications()
|
||||
self.scheduleNotifications()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func schedule() {
|
||||
func schedule() {
|
||||
UNUserNotificationCenter.current().getNotificationSettings { settings in
|
||||
switch settings.authorizationStatus {
|
||||
case .notDetermined:
|
||||
self.requestAuthorization()
|
||||
self.requestAuthorization()
|
||||
case .authorized, .provisional:
|
||||
self.scheduleNotifications()
|
||||
default:
|
||||
|
|
@ -37,6 +37,9 @@ class LocalNotificationManager {
|
|||
content.body = notification.content
|
||||
content.sound = .default
|
||||
content.interruptionLevel = .timeSensitive
|
||||
if notification.target != nil {
|
||||
content.userInfo["target"] = notification.target
|
||||
}
|
||||
|
||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
|
||||
let request = UNNotificationRequest(identifier: notification.id, content: content, trigger: trigger)
|
||||
|
|
@ -65,4 +68,5 @@ struct Notification {
|
|||
var title: String
|
||||
var subtitle: String
|
||||
var content: String
|
||||
var target: String?
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,6 +68,8 @@ func moduleConfig (config: ModuleConfig, context: NSManagedObjectContext, nodeNu
|
|||
upsertSerialModuleConfigPacket(config: config.serial, nodeNum: nodeNum, context: context)
|
||||
} else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.telemetry(config.telemetry) {
|
||||
upsertTelemetryModuleConfigPacket(config: config.telemetry, nodeNum: nodeNum, context: context)
|
||||
} else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.detectionSensor(config.detectionSensor) {
|
||||
upsertDetectionSensorModuleConfigPacket(config: config.detectionSensor, nodeNum: nodeNum, context: context)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -90,7 +92,6 @@ func myInfoPacket (myInfo: MyNodeInfo, peripheralId: String, context: NSManagedO
|
|||
myInfoEntity.peripheralId = peripheralId
|
||||
myInfoEntity.myNodeNum = Int64(myInfo.myNodeNum)
|
||||
myInfoEntity.rebootCount = Int32(myInfo.rebootCount)
|
||||
myInfoEntity.minAppVersion = Int32(bitPattern: myInfo.minAppVersion)
|
||||
do {
|
||||
try context.save()
|
||||
print("💾 Saved a new myInfo for node number: \(String(myInfo.myNodeNum))")
|
||||
|
|
@ -105,7 +106,6 @@ func myInfoPacket (myInfo: MyNodeInfo, peripheralId: String, context: NSManagedO
|
|||
fetchedMyInfo[0].peripheralId = peripheralId
|
||||
fetchedMyInfo[0].myNodeNum = Int64(myInfo.myNodeNum)
|
||||
fetchedMyInfo[0].rebootCount = Int32(myInfo.rebootCount)
|
||||
fetchedMyInfo[0].minAppVersion = Int32(bitPattern: myInfo.minAppVersion)
|
||||
|
||||
do {
|
||||
try context.save()
|
||||
|
|
@ -471,7 +471,8 @@ func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) {
|
|||
|
||||
} else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.telemetry(moduleConfig.telemetry) {
|
||||
upsertTelemetryModuleConfigPacket(config: moduleConfig.telemetry, nodeNum: Int64(packet.from), context: context)
|
||||
|
||||
} else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.detectionSensor(moduleConfig.detectionSensor) {
|
||||
upsertDetectionSensorModuleConfigPacket(config: moduleConfig.detectionSensor, nodeNum: Int64(packet.from), context: context)
|
||||
}
|
||||
|
||||
} else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getRingtoneResponse(adminMessage.getRingtoneResponse) {
|
||||
|
|
@ -580,6 +581,47 @@ func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSMana
|
|||
}
|
||||
}
|
||||
|
||||
func storeAndForwardPacket(packet: MeshPacket, connectedNodeNum: Int64, context: NSManagedObjectContext) {
|
||||
|
||||
if let storeAndForwardMessage = try? StoreAndForward(serializedData: packet.decoded.payload) {
|
||||
// RequestResponse
|
||||
switch storeAndForwardMessage.rr {
|
||||
|
||||
case .unset:
|
||||
MeshLogger.log("📮 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .routerError:
|
||||
MeshLogger.log("☠️ Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .routerHeartbeat:
|
||||
// Query any messages since the heartbeat.period. Send their ids to the store and forward node.
|
||||
MeshLogger.log("💓 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .routerPing:
|
||||
MeshLogger.log("🏓 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .routerPong:
|
||||
MeshLogger.log("🏓 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .routerBusy:
|
||||
MeshLogger.log("🐝 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .routerHistory:
|
||||
MeshLogger.log("📜 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .routerStats:
|
||||
MeshLogger.log("📊 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .clientError:
|
||||
MeshLogger.log("☠️ Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .clientHistory:
|
||||
MeshLogger.log("📜 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .clientStats:
|
||||
MeshLogger.log("📊 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .clientPing:
|
||||
MeshLogger.log("🏓 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .clientPong:
|
||||
MeshLogger.log("🏓 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .clientAbort:
|
||||
MeshLogger.log("🛑 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .UNRECOGNIZED(_):
|
||||
MeshLogger.log("📮 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManagedObjectContext) {
|
||||
|
||||
if let telemetryMessage = try? Telemetry(serializedData: packet.decoded.payload) {
|
||||
|
|
@ -644,6 +686,7 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage
|
|||
let content = UNMutableNotificationContent()
|
||||
content.title = "Critically Low Battery!"
|
||||
content.body = "Time to charge your radio, there is \(telemetry.batteryLevel)% battery remaining."
|
||||
content.userInfo["target"] = "node"
|
||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
|
||||
let uuidString = UUID().uuidString
|
||||
let request = UNNotificationRequest(identifier: uuidString, content: content, trigger: trigger)
|
||||
|
|
@ -659,7 +702,6 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage
|
|||
}
|
||||
// Update our live activity if there is one running, not available on mac iOS >= 16.2
|
||||
#if !targetEnvironment(macCatalyst)
|
||||
if #available(iOS 16.2, *) {
|
||||
|
||||
let oneMinuteLater = Calendar.current.date(byAdding: .minute, value: (Int(1) ), to: Date())!
|
||||
let date = Date.now...oneMinuteLater
|
||||
|
|
@ -675,7 +717,6 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage
|
|||
print("Updated live activity.")
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
} catch {
|
||||
|
|
@ -703,11 +744,13 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM
|
|||
let newMessage = MessageEntity(context: context)
|
||||
newMessage.messageId = Int64(packet.id)
|
||||
newMessage.messageTimestamp = Int32(bitPattern: packet.rxTime)
|
||||
newMessage.receivedTimestamp = Int32(Date().timeIntervalSince1970)
|
||||
newMessage.receivedACK = false
|
||||
newMessage.snr = packet.rxSnr
|
||||
newMessage.rssi = packet.rxRssi
|
||||
newMessage.isEmoji = packet.decoded.emoji == 1
|
||||
newMessage.channel = Int32(packet.channel)
|
||||
newMessage.portNum = Int32(packet.decoded.portnum.rawValue)
|
||||
|
||||
if packet.decoded.replyID > 0 {
|
||||
newMessage.replyID = Int64(packet.decoded.replyID)
|
||||
|
|
@ -734,7 +777,6 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM
|
|||
messageSaved = true
|
||||
|
||||
if messageSaved {
|
||||
|
||||
if newMessage.fromUser != nil && newMessage.toUser != nil && !(newMessage.fromUser?.mute ?? false) {
|
||||
// Create an iOS Notification for the received DM message and schedule it immediately
|
||||
let manager = LocalNotificationManager()
|
||||
|
|
@ -743,7 +785,9 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM
|
|||
id: ("notification.id.\(newMessage.messageId)"),
|
||||
title: "\(newMessage.fromUser?.longName ?? "unknown".localized)",
|
||||
subtitle: "AKA \(newMessage.fromUser?.shortName ?? "???")",
|
||||
content: messageText)
|
||||
content: messageText,
|
||||
target: "message"
|
||||
)
|
||||
]
|
||||
manager.schedule()
|
||||
print("💬 iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "unknown".localized)")
|
||||
|
|
@ -770,7 +814,8 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM
|
|||
id: ("notification.id.\(newMessage.messageId)"),
|
||||
title: "\(newMessage.fromUser?.longName ?? "unknown".localized)",
|
||||
subtitle: "AKA \(newMessage.fromUser?.shortName ?? "???")",
|
||||
content: messageText)
|
||||
content: messageText,
|
||||
target: "message")
|
||||
]
|
||||
manager.schedule()
|
||||
print("💬 iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "unknown".localized)")
|
||||
|
|
@ -825,7 +870,21 @@ func waypointPacket (packet: MeshPacket, context: NSManagedObjectContext) {
|
|||
waypoint.created = Date()
|
||||
do {
|
||||
try context.save()
|
||||
print("💾 Updated Node Waypoint App Packet For: \(waypoint.id)")
|
||||
print("💾 Added Node Waypoint App Packet For: \(waypoint.id)")
|
||||
let manager = LocalNotificationManager()
|
||||
let icon = String(UnicodeScalar(Int(waypoint.icon)) ?? "📍")
|
||||
let latitude = Double(waypoint.latitudeI) / 1e7
|
||||
let longitude = Double(waypoint.longitudeI) / 1e7
|
||||
manager.notifications = [
|
||||
Notification(
|
||||
id: ("notification.id.\(waypoint.id)"),
|
||||
title: "New Waypoint Received",
|
||||
subtitle: "\(icon) \(waypoint.name ?? "Dropped Pin")",
|
||||
content: "\(waypoint.longDescription ?? "\(latitude), \(longitude)")",
|
||||
target: "map"
|
||||
)
|
||||
]
|
||||
manager.schedule()
|
||||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
|
|
|
|||
|
|
@ -16,37 +16,14 @@ protocol MqttClientProxyManagerDelegate: AnyObject {
|
|||
}
|
||||
|
||||
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"
|
||||
|
|
@ -81,8 +58,6 @@ class MqttClientProxyManager {
|
|||
return
|
||||
}
|
||||
|
||||
status = .connecting
|
||||
|
||||
let clientId = "MeshtasticAppleMqttProxy-" + String(ProcessInfo().processIdentifier)
|
||||
|
||||
mqttClientProxy = CocoaMQTT(clientID: clientId, host: host, port: UInt16(port))
|
||||
|
|
@ -103,17 +78,14 @@ class MqttClientProxyManager {
|
|||
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) {
|
||||
func subscribe(topic: String, qos: CocoaMQTTQoS) {
|
||||
print("📲 MQTT Client Proxy subscribed to: " + topic)
|
||||
let qos = CocoaMQTTQoS(rawValue :UInt8(qos.rawValue))!
|
||||
mqttClientProxy?.subscribe(topic, qos: qos)
|
||||
}
|
||||
|
||||
|
|
@ -122,21 +94,16 @@ class MqttClientProxyManager {
|
|||
print("📲 MQTT Client Proxy unsubscribe for: " + topic)
|
||||
}
|
||||
|
||||
func publish(message: String, topic: String, qos: MqttQos) {
|
||||
let qos = CocoaMQTTQoS(rawValue :UInt8(qos.rawValue))!
|
||||
func publish(message: String, topic: String, qos: CocoaMQTTQoS) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -169,22 +136,16 @@ extension MqttClientProxyManager: CocoaMQTTDelegate {
|
|||
}
|
||||
print(errorDescription)
|
||||
delegate?.onMqttError(message: errorDescription)
|
||||
|
||||
//self.disconnect() // Stop reconnecting
|
||||
//mqttSettings.isConnected = false // Disable automatic connect on start
|
||||
self.disconnect()
|
||||
}
|
||||
|
||||
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 {
|
||||
if let error = err {
|
||||
delegate?.onMqttError(message: error.localizedDescription)
|
||||
}
|
||||
|
||||
status = err == nil ? .disconnected : .error
|
||||
delegate?.onMqttDisconnected()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>MeshtasticDataModelV15.xcdatamodel</string>
|
||||
<string>MeshtasticDataModelV16.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,346 @@
|
|||
<?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="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="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="portNum" optional="YES" 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="receivedTimestamp" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="replyID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<relationship name="fromUser" optional="YES" maxCount="1" deletionRule="Nullify" ordered="YES" destinationEntity="UserEntity" inverseName="sentMessages" inverseEntity="UserEntity"/>
|
||||
<relationship name="toUser" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="receivedMessages" inverseEntity="UserEntity"/>
|
||||
<fetchedProperty name="tapbacks" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="replyID == $FETCH_SOURCE.messageId AND isEmoji == true"/>
|
||||
</fetchedProperty>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="messageId"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="MQTTConfigEntity" representedClassName="MQTTConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="address" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="encryptionEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="jsonEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="password" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<attribute name="proxyToClientEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="root" optional="YES" attributeType="String" defaultValueString="msh"/>
|
||||
<attribute name="tlsEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="username" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<relationship name="mqttConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="mqttConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="MyInfoEntity" representedClassName="MyInfoEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="adminIndex" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="bleName" optional="YES" attributeType="String"/>
|
||||
<attribute name="minAppVersion" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="myNodeNum" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="peripheralId" optional="YES" attributeType="String"/>
|
||||
<attribute name="rebootCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="channels" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="ChannelEntity" inverseName="myInfoChannel" inverseEntity="ChannelEntity"/>
|
||||
<relationship name="myInfoNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="myInfo" inverseEntity="NodeInfoEntity"/>
|
||||
<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="detectionSensorConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DetectionSensorConfigEntity" inverseName="detectionSensorConfigNode" inverseEntity="DetectionSensorConfigEntity"/>
|
||||
<relationship name="deviceConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DeviceConfigEntity" inverseName="deviceConfigNode" inverseEntity="DeviceConfigEntity"/>
|
||||
<relationship name="displayConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DisplayConfigEntity" inverseName="displayConfigNode" inverseEntity="DisplayConfigEntity"/>
|
||||
<relationship name="externalNotificationConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ExternalNotificationConfigEntity" inverseName="externalNotificationConfigNode" inverseEntity="ExternalNotificationConfigEntity"/>
|
||||
<relationship name="loRaConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="LoRaConfigEntity" inverseName="loRaConfigNode" inverseEntity="LoRaConfigEntity"/>
|
||||
<relationship name="metadata" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DeviceMetadataEntity" inverseName="metadataNode" inverseEntity="DeviceMetadataEntity"/>
|
||||
<relationship name="mqttConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MQTTConfigEntity" inverseName="mqttConfigNode" inverseEntity="MQTTConfigEntity"/>
|
||||
<relationship name="myInfo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="myInfoNode" inverseEntity="MyInfoEntity"/>
|
||||
<relationship name="networkConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NetworkConfigEntity" inverseName="networkConfigNode" inverseEntity="NetworkConfigEntity"/>
|
||||
<relationship name="positionConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PositionConfigEntity" inverseName="positionConfigNode" inverseEntity="PositionConfigEntity"/>
|
||||
<relationship name="positions" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PositionEntity" inverseName="nodePosition" inverseEntity="PositionEntity"/>
|
||||
<relationship name="rangeTestConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RangeTestConfigEntity" inverseName="rangeTestConfigNode" inverseEntity="RangeTestConfigEntity"/>
|
||||
<relationship name="rtttlConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RTTTLConfigEntity" inverseName="rtttlConfigNode" inverseEntity="RTTTLConfigEntity"/>
|
||||
<relationship name="serialConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SerialConfigEntity" inverseName="serialConfigNode" inverseEntity="SerialConfigEntity"/>
|
||||
<relationship name="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>
|
||||
|
|
@ -4,8 +4,8 @@ import SwiftUI
|
|||
import CoreData
|
||||
|
||||
@main
|
||||
struct MeshtasticAppleApp: App {
|
||||
|
||||
struct MeshtasticAppleApp : App {
|
||||
@UIApplicationDelegateAdaptor(MeshtasticAppDelegate.self) var appDelegate
|
||||
let persistenceController = PersistenceController.shared
|
||||
@ObservedObject private var bleManager: BLEManager = BLEManager()
|
||||
@Environment(\.scenePhase) var scenePhase
|
||||
|
|
@ -13,10 +13,11 @@ struct MeshtasticAppleApp: App {
|
|||
@State var saveChannels = false
|
||||
@State var incomingUrl: URL?
|
||||
@State var channelSettings: String?
|
||||
|
||||
@StateObject var appState = AppState.shared
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
ContentView()
|
||||
.environment(\.managedObjectContext, persistenceController.container.viewContext)
|
||||
.environmentObject(bleManager)
|
||||
.sheet(isPresented: $saveChannels) {
|
||||
|
|
@ -45,7 +46,6 @@ struct MeshtasticAppleApp: App {
|
|||
|
||||
print("Some sort of URL was received \(url)")
|
||||
self.incomingUrl = url
|
||||
|
||||
if url.absoluteString.lowercased().contains("meshtastic.org/e/#") {
|
||||
if let components = self.incomingUrl?.absoluteString.components(separatedBy: "#") {
|
||||
self.channelSettings = components.last!
|
||||
|
|
@ -115,5 +115,11 @@ struct MeshtasticAppleApp: App {
|
|||
print("💥 Apple must have changed something")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AppState: ObservableObject {
|
||||
static let shared = AppState()
|
||||
|
||||
@Published var tabSelection: Tab = .ble
|
||||
}
|
||||
|
|
|
|||
38
Meshtastic/MeshtasticAppDelegate.swift
Normal file
38
Meshtastic/MeshtasticAppDelegate.swift
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
//
|
||||
// MeshtasticAppDelegate.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Ben on 8/20/23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
class MeshtasticAppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
||||
print("App launched!")
|
||||
UNUserNotificationCenter.current().delegate = self
|
||||
return true
|
||||
}
|
||||
|
||||
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
|
||||
}
|
||||
|
||||
// This method is called when user clicked on the notification
|
||||
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void)
|
||||
{
|
||||
let userInfo = response.notification.request.content.userInfo
|
||||
let targetValue = userInfo["target"] as? String
|
||||
if targetValue == "map" {
|
||||
AppState.shared.tabSelection = Tab.map
|
||||
}
|
||||
else if targetValue == "message" {
|
||||
AppState.shared.tabSelection = Tab.messages
|
||||
}
|
||||
else if targetValue == "node" {
|
||||
AppState.shared.tabSelection = Tab.nodes
|
||||
}
|
||||
|
||||
completionHandler()
|
||||
}
|
||||
}
|
||||
21
Meshtastic/Persistence/MessageEntityExtension.swift
Normal file
21
Meshtastic/Persistence/MessageEntityExtension.swift
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// MessageEntityExtension.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Ben on 8/22/23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
import CoreData
|
||||
import CoreLocation
|
||||
import MapKit
|
||||
import SwiftUI
|
||||
|
||||
extension MessageEntity {
|
||||
|
||||
var timestamp: Date {
|
||||
let time = messageTimestamp <= 0 ? receivedTimestamp : messageTimestamp
|
||||
return Date(timeIntervalSince1970: TimeInterval(time))
|
||||
}
|
||||
}
|
||||
|
|
@ -60,3 +60,23 @@ public func getWaypoint(id: Int64, context: NSManagedObjectContext) -> WaypointE
|
|||
}
|
||||
return WaypointEntity(context: context)
|
||||
}
|
||||
|
||||
|
||||
public func getDetectionSensorMessages(nodeNum: Int64?, context: NSManagedObjectContext) -> [MessageEntity] {
|
||||
|
||||
let fetchDetectionMessagesPredicate: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MessageEntity")
|
||||
fetchDetectionMessagesPredicate.predicate = NSPredicate(format: "portNum == %d", Int32(PortNum.detectionSensorApp.rawValue))
|
||||
|
||||
do {
|
||||
let fetched = try context.fetch(fetchDetectionMessagesPredicate) as? [MessageEntity] ?? []
|
||||
if nodeNum == nil {
|
||||
return fetched.reversed()
|
||||
}
|
||||
return fetched.filter { message in
|
||||
return message.fromUser?.num == nodeNum!
|
||||
}.reversed()
|
||||
}
|
||||
catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -972,3 +972,64 @@ func upsertTelemetryModuleConfigPacket(config: Meshtastic.ModuleConfig.Telemetry
|
|||
print("💥 Fetching node for core data TelemetryConfigEntity failed: \(nsError)")
|
||||
}
|
||||
}
|
||||
|
||||
func upsertDetectionSensorModuleConfigPacket(config: Meshtastic.ModuleConfig.DetectionSensorConfig, nodeNum: Int64, context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.detectionsensor.config %@".localized, String(nodeNum))
|
||||
MeshLogger.log("📈 \(logString)")
|
||||
|
||||
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
||||
do {
|
||||
|
||||
guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else {
|
||||
return
|
||||
}
|
||||
// Found a node, save Detection Sensor Config
|
||||
if !fetchedNode.isEmpty {
|
||||
|
||||
if fetchedNode[0].detectionSensorConfig == nil {
|
||||
|
||||
let newConfig = DetectionSensorConfigEntity(context: context)
|
||||
newConfig.enabled = config.enabled
|
||||
newConfig.sendBell = config.sendBell
|
||||
newConfig.name = config.name
|
||||
|
||||
newConfig.monitorPin = Int32(config.monitorPin)
|
||||
newConfig.detectionTriggeredHigh = config.detectionTriggeredHigh
|
||||
newConfig.usePullup = config.usePullup
|
||||
newConfig.minimumBroadcastSecs = Int32(config.minimumBroadcastSecs)
|
||||
newConfig.stateBroadcastSecs = Int32(config.stateBroadcastSecs)
|
||||
fetchedNode[0].detectionSensorConfig = newConfig
|
||||
|
||||
} else {
|
||||
fetchedNode[0].detectionSensorConfig?.enabled = config.enabled
|
||||
fetchedNode[0].detectionSensorConfig?.sendBell = config.sendBell
|
||||
fetchedNode[0].detectionSensorConfig?.name = config.name
|
||||
fetchedNode[0].detectionSensorConfig?.monitorPin = Int32(config.monitorPin)
|
||||
fetchedNode[0].detectionSensorConfig?.usePullup = config.usePullup
|
||||
fetchedNode[0].detectionSensorConfig?.detectionTriggeredHigh = config.detectionTriggeredHigh
|
||||
fetchedNode[0].detectionSensorConfig?.minimumBroadcastSecs = Int32(config.minimumBroadcastSecs)
|
||||
fetchedNode[0].detectionSensorConfig?.stateBroadcastSecs = Int32(config.stateBroadcastSecs)
|
||||
}
|
||||
|
||||
do {
|
||||
try context.save()
|
||||
print("💾 Updated Detection Sensor Module Config for node number: \(String(nodeNum))")
|
||||
|
||||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
print("💥 Error Updating Core Data DetectionSensorConfigEntity: \(nsError)")
|
||||
}
|
||||
|
||||
} else {
|
||||
print("💥 No Nodes found in local database matching node number \(nodeNum) unable to save Detection Sensor Module Config")
|
||||
}
|
||||
|
||||
} catch {
|
||||
let nsError = error as NSError
|
||||
print("💥 Fetching node for core data DetectionSensorConfigEntity failed: \(nsError)")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -734,6 +734,18 @@ struct AdminMessage {
|
|||
///
|
||||
/// TODO: REPLACE
|
||||
case remotehardwareConfig // = 8
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
case neighborinfoConfig // = 9
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
case ambientlightingConfig // = 10
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
case detectionsensorConfig // = 11
|
||||
case UNRECOGNIZED(Int)
|
||||
|
||||
init() {
|
||||
|
|
@ -751,6 +763,9 @@ struct AdminMessage {
|
|||
case 6: self = .cannedmsgConfig
|
||||
case 7: self = .audioConfig
|
||||
case 8: self = .remotehardwareConfig
|
||||
case 9: self = .neighborinfoConfig
|
||||
case 10: self = .ambientlightingConfig
|
||||
case 11: self = .detectionsensorConfig
|
||||
default: self = .UNRECOGNIZED(rawValue)
|
||||
}
|
||||
}
|
||||
|
|
@ -766,6 +781,9 @@ struct AdminMessage {
|
|||
case .cannedmsgConfig: return 6
|
||||
case .audioConfig: return 7
|
||||
case .remotehardwareConfig: return 8
|
||||
case .neighborinfoConfig: return 9
|
||||
case .ambientlightingConfig: return 10
|
||||
case .detectionsensorConfig: return 11
|
||||
case .UNRECOGNIZED(let i): return i
|
||||
}
|
||||
}
|
||||
|
|
@ -802,6 +820,9 @@ extension AdminMessage.ModuleConfigType: CaseIterable {
|
|||
.cannedmsgConfig,
|
||||
.audioConfig,
|
||||
.remotehardwareConfig,
|
||||
.neighborinfoConfig,
|
||||
.ambientlightingConfig,
|
||||
.detectionsensorConfig,
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -1412,6 +1433,9 @@ extension AdminMessage.ModuleConfigType: SwiftProtobuf._ProtoNameProviding {
|
|||
6: .same(proto: "CANNEDMSG_CONFIG"),
|
||||
7: .same(proto: "AUDIO_CONFIG"),
|
||||
8: .same(proto: "REMOTEHARDWARE_CONFIG"),
|
||||
9: .same(proto: "NEIGHBORINFO_CONFIG"),
|
||||
10: .same(proto: "AMBIENTLIGHTING_CONFIG"),
|
||||
11: .same(proto: "DETECTIONSENSOR_CONFIG"),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -368,7 +368,7 @@ struct Config {
|
|||
var broadcastSmartMinimumDistance: UInt32 = 0
|
||||
|
||||
///
|
||||
/// The minumum number of seconds (since the last send) before we can send a position to the mesh if position_broadcast_smart_enabled
|
||||
/// The minimum number of seconds (since the last send) before we can send a position to the mesh if position_broadcast_smart_enabled
|
||||
var broadcastSmartMinimumIntervalSecs: UInt32 = 0
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
|
@ -506,13 +506,6 @@ struct Config {
|
|||
/// 0 for default of 1 minute
|
||||
var waitBluetoothSecs: UInt32 = 0
|
||||
|
||||
///
|
||||
/// Mesh Super Deep Sleep Timeout Seconds
|
||||
/// While in Light Sleep if this value is exceeded we will lower into super deep sleep
|
||||
/// for sds_secs (default 1 year) or a button press
|
||||
/// 0 for default of two hours, MAXUINT for disabled
|
||||
var meshSdsTimeoutSecs: UInt32 = 0
|
||||
|
||||
///
|
||||
/// Super Deep Sleep Seconds
|
||||
/// While in Light Sleep if mesh_sds_timeout_secs is exceeded we will lower into super deep sleep
|
||||
|
|
@ -1805,7 +1798,6 @@ extension Config.PowerConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImple
|
|||
2: .standard(proto: "on_battery_shutdown_after_secs"),
|
||||
3: .standard(proto: "adc_multiplier_override"),
|
||||
4: .standard(proto: "wait_bluetooth_secs"),
|
||||
5: .standard(proto: "mesh_sds_timeout_secs"),
|
||||
6: .standard(proto: "sds_secs"),
|
||||
7: .standard(proto: "ls_secs"),
|
||||
8: .standard(proto: "min_wake_secs"),
|
||||
|
|
@ -1822,7 +1814,6 @@ extension Config.PowerConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImple
|
|||
case 2: try { try decoder.decodeSingularUInt32Field(value: &self.onBatteryShutdownAfterSecs) }()
|
||||
case 3: try { try decoder.decodeSingularFloatField(value: &self.adcMultiplierOverride) }()
|
||||
case 4: try { try decoder.decodeSingularUInt32Field(value: &self.waitBluetoothSecs) }()
|
||||
case 5: try { try decoder.decodeSingularUInt32Field(value: &self.meshSdsTimeoutSecs) }()
|
||||
case 6: try { try decoder.decodeSingularUInt32Field(value: &self.sdsSecs) }()
|
||||
case 7: try { try decoder.decodeSingularUInt32Field(value: &self.lsSecs) }()
|
||||
case 8: try { try decoder.decodeSingularUInt32Field(value: &self.minWakeSecs) }()
|
||||
|
|
@ -1845,9 +1836,6 @@ extension Config.PowerConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImple
|
|||
if self.waitBluetoothSecs != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.waitBluetoothSecs, fieldNumber: 4)
|
||||
}
|
||||
if self.meshSdsTimeoutSecs != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.meshSdsTimeoutSecs, fieldNumber: 5)
|
||||
}
|
||||
if self.sdsSecs != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.sdsSecs, fieldNumber: 6)
|
||||
}
|
||||
|
|
@ -1868,7 +1856,6 @@ extension Config.PowerConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImple
|
|||
if lhs.onBatteryShutdownAfterSecs != rhs.onBatteryShutdownAfterSecs {return false}
|
||||
if lhs.adcMultiplierOverride != rhs.adcMultiplierOverride {return false}
|
||||
if lhs.waitBluetoothSecs != rhs.waitBluetoothSecs {return false}
|
||||
if lhs.meshSdsTimeoutSecs != rhs.meshSdsTimeoutSecs {return false}
|
||||
if lhs.sdsSecs != rhs.sdsSecs {return false}
|
||||
if lhs.lsSecs != rhs.lsSecs {return false}
|
||||
if lhs.minWakeSecs != rhs.minWakeSecs {return false}
|
||||
|
|
|
|||
|
|
@ -108,14 +108,6 @@ struct DeviceState {
|
|||
/// Clears the value of `owner`. Subsequent reads from it will return its default value.
|
||||
mutating func clearOwner() {_uniqueStorage()._owner = nil}
|
||||
|
||||
///
|
||||
/// Deprecated in 2.1.x
|
||||
/// Old node_db. See NodeInfoLite node_db_lite
|
||||
var nodeDb: [NodeInfo] {
|
||||
get {return _storage._nodeDb}
|
||||
set {_uniqueStorage()._nodeDb = newValue}
|
||||
}
|
||||
|
||||
///
|
||||
/// Received packets saved for delivery to the phone
|
||||
var receiveQueue: [MeshPacket] {
|
||||
|
|
@ -446,7 +438,6 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati
|
|||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
2: .standard(proto: "my_node"),
|
||||
3: .same(proto: "owner"),
|
||||
4: .standard(proto: "node_db"),
|
||||
5: .standard(proto: "receive_queue"),
|
||||
8: .same(proto: "version"),
|
||||
7: .standard(proto: "rx_text_message"),
|
||||
|
|
@ -460,7 +451,6 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati
|
|||
fileprivate class _StorageClass {
|
||||
var _myNode: MyNodeInfo? = nil
|
||||
var _owner: User? = nil
|
||||
var _nodeDb: [NodeInfo] = []
|
||||
var _receiveQueue: [MeshPacket] = []
|
||||
var _version: UInt32 = 0
|
||||
var _rxTextMessage: MeshPacket? = nil
|
||||
|
|
@ -477,7 +467,6 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati
|
|||
init(copying source: _StorageClass) {
|
||||
_myNode = source._myNode
|
||||
_owner = source._owner
|
||||
_nodeDb = source._nodeDb
|
||||
_receiveQueue = source._receiveQueue
|
||||
_version = source._version
|
||||
_rxTextMessage = source._rxTextMessage
|
||||
|
|
@ -506,7 +495,6 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati
|
|||
switch fieldNumber {
|
||||
case 2: try { try decoder.decodeSingularMessageField(value: &_storage._myNode) }()
|
||||
case 3: try { try decoder.decodeSingularMessageField(value: &_storage._owner) }()
|
||||
case 4: try { try decoder.decodeRepeatedMessageField(value: &_storage._nodeDb) }()
|
||||
case 5: try { try decoder.decodeRepeatedMessageField(value: &_storage._receiveQueue) }()
|
||||
case 7: try { try decoder.decodeSingularMessageField(value: &_storage._rxTextMessage) }()
|
||||
case 8: try { try decoder.decodeSingularUInt32Field(value: &_storage._version) }()
|
||||
|
|
@ -533,9 +521,6 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati
|
|||
try { if let v = _storage._owner {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 3)
|
||||
} }()
|
||||
if !_storage._nodeDb.isEmpty {
|
||||
try visitor.visitRepeatedMessageField(value: _storage._nodeDb, fieldNumber: 4)
|
||||
}
|
||||
if !_storage._receiveQueue.isEmpty {
|
||||
try visitor.visitRepeatedMessageField(value: _storage._receiveQueue, fieldNumber: 5)
|
||||
}
|
||||
|
|
@ -571,7 +556,6 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati
|
|||
let rhs_storage = _args.1
|
||||
if _storage._myNode != rhs_storage._myNode {return false}
|
||||
if _storage._owner != rhs_storage._owner {return false}
|
||||
if _storage._nodeDb != rhs_storage._nodeDb {return false}
|
||||
if _storage._receiveQueue != rhs_storage._receiveQueue {return false}
|
||||
if _storage._version != rhs_storage._version {return false}
|
||||
if _storage._rxTextMessage != rhs_storage._rxTextMessage {return false}
|
||||
|
|
|
|||
|
|
@ -233,6 +233,28 @@ struct LocalModuleConfig {
|
|||
/// Clears the value of `neighborInfo`. Subsequent reads from it will return its default value.
|
||||
mutating func clearNeighborInfo() {_uniqueStorage()._neighborInfo = nil}
|
||||
|
||||
///
|
||||
/// The part of the config that is specific to the Ambient Lighting module
|
||||
var ambientLighting: ModuleConfig.AmbientLightingConfig {
|
||||
get {return _storage._ambientLighting ?? ModuleConfig.AmbientLightingConfig()}
|
||||
set {_uniqueStorage()._ambientLighting = newValue}
|
||||
}
|
||||
/// Returns true if `ambientLighting` has been explicitly set.
|
||||
var hasAmbientLighting: Bool {return _storage._ambientLighting != nil}
|
||||
/// Clears the value of `ambientLighting`. Subsequent reads from it will return its default value.
|
||||
mutating func clearAmbientLighting() {_uniqueStorage()._ambientLighting = nil}
|
||||
|
||||
///
|
||||
/// The part of the config that is specific to the Detection Sensor module
|
||||
var detectionSensor: ModuleConfig.DetectionSensorConfig {
|
||||
get {return _storage._detectionSensor ?? ModuleConfig.DetectionSensorConfig()}
|
||||
set {_uniqueStorage()._detectionSensor = newValue}
|
||||
}
|
||||
/// Returns true if `detectionSensor` has been explicitly set.
|
||||
var hasDetectionSensor: Bool {return _storage._detectionSensor != nil}
|
||||
/// Clears the value of `detectionSensor`. Subsequent reads from it will return its default value.
|
||||
mutating func clearDetectionSensor() {_uniqueStorage()._detectionSensor = nil}
|
||||
|
||||
///
|
||||
/// A version integer used to invalidate old save files when we make
|
||||
/// incompatible changes This integer is set at build time and is private to
|
||||
|
|
@ -395,6 +417,8 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
|
|||
9: .same(proto: "audio"),
|
||||
10: .standard(proto: "remote_hardware"),
|
||||
11: .standard(proto: "neighbor_info"),
|
||||
12: .standard(proto: "ambient_lighting"),
|
||||
13: .standard(proto: "detection_sensor"),
|
||||
8: .same(proto: "version"),
|
||||
]
|
||||
|
||||
|
|
@ -409,6 +433,8 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
|
|||
var _audio: ModuleConfig.AudioConfig? = nil
|
||||
var _remoteHardware: ModuleConfig.RemoteHardwareConfig? = nil
|
||||
var _neighborInfo: ModuleConfig.NeighborInfoConfig? = nil
|
||||
var _ambientLighting: ModuleConfig.AmbientLightingConfig? = nil
|
||||
var _detectionSensor: ModuleConfig.DetectionSensorConfig? = nil
|
||||
var _version: UInt32 = 0
|
||||
|
||||
static let defaultInstance = _StorageClass()
|
||||
|
|
@ -426,6 +452,8 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
|
|||
_audio = source._audio
|
||||
_remoteHardware = source._remoteHardware
|
||||
_neighborInfo = source._neighborInfo
|
||||
_ambientLighting = source._ambientLighting
|
||||
_detectionSensor = source._detectionSensor
|
||||
_version = source._version
|
||||
}
|
||||
}
|
||||
|
|
@ -456,6 +484,8 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
|
|||
case 9: try { try decoder.decodeSingularMessageField(value: &_storage._audio) }()
|
||||
case 10: try { try decoder.decodeSingularMessageField(value: &_storage._remoteHardware) }()
|
||||
case 11: try { try decoder.decodeSingularMessageField(value: &_storage._neighborInfo) }()
|
||||
case 12: try { try decoder.decodeSingularMessageField(value: &_storage._ambientLighting) }()
|
||||
case 13: try { try decoder.decodeSingularMessageField(value: &_storage._detectionSensor) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
|
@ -501,6 +531,12 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
|
|||
try { if let v = _storage._neighborInfo {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 11)
|
||||
} }()
|
||||
try { if let v = _storage._ambientLighting {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 12)
|
||||
} }()
|
||||
try { if let v = _storage._detectionSensor {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 13)
|
||||
} }()
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
|
@ -520,6 +556,8 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
|
|||
if _storage._audio != rhs_storage._audio {return false}
|
||||
if _storage._remoteHardware != rhs_storage._remoteHardware {return false}
|
||||
if _storage._neighborInfo != rhs_storage._neighborInfo {return false}
|
||||
if _storage._ambientLighting != rhs_storage._ambientLighting {return false}
|
||||
if _storage._detectionSensor != rhs_storage._detectionSensor {return false}
|
||||
if _storage._version != rhs_storage._version {return false}
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -194,6 +194,16 @@ struct ModuleConfig {
|
|||
set {payloadVariant = .ambientLighting(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
var detectionSensor: ModuleConfig.DetectionSensorConfig {
|
||||
get {
|
||||
if case .detectionSensor(let v)? = payloadVariant {return v}
|
||||
return ModuleConfig.DetectionSensorConfig()
|
||||
}
|
||||
set {payloadVariant = .detectionSensor(newValue)}
|
||||
}
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
///
|
||||
|
|
@ -232,6 +242,9 @@ struct ModuleConfig {
|
|||
///
|
||||
/// TODO: REPLACE
|
||||
case ambientLighting(ModuleConfig.AmbientLightingConfig)
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
case detectionSensor(ModuleConfig.DetectionSensorConfig)
|
||||
|
||||
#if !swift(>=4.1)
|
||||
static func ==(lhs: ModuleConfig.OneOf_PayloadVariant, rhs: ModuleConfig.OneOf_PayloadVariant) -> Bool {
|
||||
|
|
@ -283,6 +296,10 @@ struct ModuleConfig {
|
|||
guard case .ambientLighting(let l) = lhs, case .ambientLighting(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.detectionSensor, .detectionSensor): return {
|
||||
guard case .detectionSensor(let l) = lhs, case .detectionSensor(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
|
@ -392,6 +409,57 @@ struct ModuleConfig {
|
|||
init() {}
|
||||
}
|
||||
|
||||
///
|
||||
/// Detection Sensor Module Config
|
||||
struct DetectionSensorConfig {
|
||||
// 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.
|
||||
|
||||
///
|
||||
/// Whether the Module is enabled
|
||||
var enabled: Bool = false
|
||||
|
||||
///
|
||||
/// Interval in seconds of how often we can send a message to the mesh when a state change is detected
|
||||
var minimumBroadcastSecs: UInt32 = 0
|
||||
|
||||
///
|
||||
/// Interval in seconds of how often we should send a message to the mesh with the current state regardless of changes
|
||||
/// When set to 0, only state changes will be broadcasted
|
||||
/// Works as a sort of status heartbeat for peace of mind
|
||||
var stateBroadcastSecs: UInt32 = 0
|
||||
|
||||
///
|
||||
/// Send ASCII bell with alert message
|
||||
/// Useful for triggering ext. notification on bell
|
||||
var sendBell: Bool = false
|
||||
|
||||
///
|
||||
/// Friendly name used to format message sent to mesh
|
||||
/// Example: A name "Motion" would result in a message "Motion detected"
|
||||
/// Maximum length of 20 characters
|
||||
var name: String = String()
|
||||
|
||||
///
|
||||
/// GPIO pin to monitor for state changes
|
||||
var monitorPin: UInt32 = 0
|
||||
|
||||
///
|
||||
/// Whether or not the GPIO pin state detection is triggered on HIGH (1)
|
||||
/// Otherwise LOW (0)
|
||||
var detectionTriggeredHigh: Bool = false
|
||||
|
||||
///
|
||||
/// Whether or not use INPUT_PULLUP mode for GPIO pin
|
||||
/// Only applicable if the board uses pull-up resistors on the pin
|
||||
var usePullup: Bool = false
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
}
|
||||
|
||||
///
|
||||
/// Audio Config for codec2 voice
|
||||
struct AudioConfig {
|
||||
|
|
@ -1080,6 +1148,7 @@ extension ModuleConfig.OneOf_PayloadVariant: @unchecked Sendable {}
|
|||
extension ModuleConfig.MQTTConfig: @unchecked Sendable {}
|
||||
extension ModuleConfig.RemoteHardwareConfig: @unchecked Sendable {}
|
||||
extension ModuleConfig.NeighborInfoConfig: @unchecked Sendable {}
|
||||
extension ModuleConfig.DetectionSensorConfig: @unchecked Sendable {}
|
||||
extension ModuleConfig.AudioConfig: @unchecked Sendable {}
|
||||
extension ModuleConfig.AudioConfig.Audio_Baud: @unchecked Sendable {}
|
||||
extension ModuleConfig.SerialConfig: @unchecked Sendable {}
|
||||
|
|
@ -1121,6 +1190,7 @@ extension ModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
9: .standard(proto: "remote_hardware"),
|
||||
10: .standard(proto: "neighbor_info"),
|
||||
11: .standard(proto: "ambient_lighting"),
|
||||
12: .standard(proto: "detection_sensor"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
|
|
@ -1272,6 +1342,19 @@ extension ModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
self.payloadVariant = .ambientLighting(v)
|
||||
}
|
||||
}()
|
||||
case 12: try {
|
||||
var v: ModuleConfig.DetectionSensorConfig?
|
||||
var hadOneofValue = false
|
||||
if let current = self.payloadVariant {
|
||||
hadOneofValue = true
|
||||
if case .detectionSensor(let m) = current {v = m}
|
||||
}
|
||||
try decoder.decodeSingularMessageField(value: &v)
|
||||
if let v = v {
|
||||
if hadOneofValue {try decoder.handleConflictingOneOf()}
|
||||
self.payloadVariant = .detectionSensor(v)
|
||||
}
|
||||
}()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
|
@ -1327,6 +1410,10 @@ extension ModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
guard case .ambientLighting(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 11)
|
||||
}()
|
||||
case .detectionSensor?: try {
|
||||
guard case .detectionSensor(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 12)
|
||||
}()
|
||||
case nil: break
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
|
|
@ -1501,6 +1588,80 @@ extension ModuleConfig.NeighborInfoConfig: SwiftProtobuf.Message, SwiftProtobuf.
|
|||
}
|
||||
}
|
||||
|
||||
extension ModuleConfig.DetectionSensorConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = ModuleConfig.protoMessageName + ".DetectionSensorConfig"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .same(proto: "enabled"),
|
||||
2: .standard(proto: "minimum_broadcast_secs"),
|
||||
3: .standard(proto: "state_broadcast_secs"),
|
||||
4: .standard(proto: "send_bell"),
|
||||
5: .same(proto: "name"),
|
||||
6: .standard(proto: "monitor_pin"),
|
||||
7: .standard(proto: "detection_triggered_high"),
|
||||
8: .standard(proto: "use_pullup"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try { try decoder.decodeSingularBoolField(value: &self.enabled) }()
|
||||
case 2: try { try decoder.decodeSingularUInt32Field(value: &self.minimumBroadcastSecs) }()
|
||||
case 3: try { try decoder.decodeSingularUInt32Field(value: &self.stateBroadcastSecs) }()
|
||||
case 4: try { try decoder.decodeSingularBoolField(value: &self.sendBell) }()
|
||||
case 5: try { try decoder.decodeSingularStringField(value: &self.name) }()
|
||||
case 6: try { try decoder.decodeSingularUInt32Field(value: &self.monitorPin) }()
|
||||
case 7: try { try decoder.decodeSingularBoolField(value: &self.detectionTriggeredHigh) }()
|
||||
case 8: try { try decoder.decodeSingularBoolField(value: &self.usePullup) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
if self.enabled != false {
|
||||
try visitor.visitSingularBoolField(value: self.enabled, fieldNumber: 1)
|
||||
}
|
||||
if self.minimumBroadcastSecs != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.minimumBroadcastSecs, fieldNumber: 2)
|
||||
}
|
||||
if self.stateBroadcastSecs != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.stateBroadcastSecs, fieldNumber: 3)
|
||||
}
|
||||
if self.sendBell != false {
|
||||
try visitor.visitSingularBoolField(value: self.sendBell, fieldNumber: 4)
|
||||
}
|
||||
if !self.name.isEmpty {
|
||||
try visitor.visitSingularStringField(value: self.name, fieldNumber: 5)
|
||||
}
|
||||
if self.monitorPin != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.monitorPin, fieldNumber: 6)
|
||||
}
|
||||
if self.detectionTriggeredHigh != false {
|
||||
try visitor.visitSingularBoolField(value: self.detectionTriggeredHigh, fieldNumber: 7)
|
||||
}
|
||||
if self.usePullup != false {
|
||||
try visitor.visitSingularBoolField(value: self.usePullup, fieldNumber: 8)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: ModuleConfig.DetectionSensorConfig, rhs: ModuleConfig.DetectionSensorConfig) -> Bool {
|
||||
if lhs.enabled != rhs.enabled {return false}
|
||||
if lhs.minimumBroadcastSecs != rhs.minimumBroadcastSecs {return false}
|
||||
if lhs.stateBroadcastSecs != rhs.stateBroadcastSecs {return false}
|
||||
if lhs.sendBell != rhs.sendBell {return false}
|
||||
if lhs.name != rhs.name {return false}
|
||||
if lhs.monitorPin != rhs.monitorPin {return false}
|
||||
if lhs.detectionTriggeredHigh != rhs.detectionTriggeredHigh {return false}
|
||||
if lhs.usePullup != rhs.usePullup {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension ModuleConfig.AudioConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = ModuleConfig.protoMessageName + ".AudioConfig"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
|
|
|
|||
|
|
@ -40,60 +40,81 @@ enum PortNum: SwiftProtobuf.Enum {
|
|||
/// Deprecated: do not use in new code (formerly called OPAQUE)
|
||||
/// A message sent from a device outside of the mesh, in a form the mesh does not understand
|
||||
/// NOTE: This must be 0, because it is documented in IMeshService.aidl to be so
|
||||
/// ENCODING: binary undefined
|
||||
case unknownApp // = 0
|
||||
|
||||
///
|
||||
/// A simple UTF-8 text message, which even the little micros in the mesh
|
||||
/// can understand and show on their screen eventually in some circumstances
|
||||
/// even signal might send messages in this form (see below)
|
||||
/// ENCODING: UTF-8 Plaintext (?)
|
||||
case textMessageApp // = 1
|
||||
|
||||
///
|
||||
/// Reserved for built-in GPIO/example app.
|
||||
/// See remote_hardware.proto/HardwareMessage for details on the message sent/received to this port number
|
||||
/// ENCODING: Protobuf
|
||||
case remoteHardwareApp // = 2
|
||||
|
||||
///
|
||||
/// The built-in position messaging app.
|
||||
/// Payload is a [Position](/docs/developers/protobufs/api#position) message
|
||||
/// ENCODING: Protobuf
|
||||
case positionApp // = 3
|
||||
|
||||
///
|
||||
/// The built-in user info app.
|
||||
/// Payload is a [User](/docs/developers/protobufs/api#user) message
|
||||
/// ENCODING: Protobuf
|
||||
case nodeinfoApp // = 4
|
||||
|
||||
///
|
||||
/// Protocol control packets for mesh protocol use.
|
||||
/// Payload is a [Routing](/docs/developers/protobufs/api#routing) message
|
||||
/// ENCODING: Protobuf
|
||||
case routingApp // = 5
|
||||
|
||||
///
|
||||
/// Admin control packets.
|
||||
/// Payload is a [AdminMessage](/docs/developers/protobufs/api#adminmessage) message
|
||||
/// ENCODING: Protobuf
|
||||
case adminApp // = 6
|
||||
|
||||
///
|
||||
/// Compressed TEXT_MESSAGE payloads.
|
||||
/// ENCODING: UTF-8 Plaintext (?) with Unishox2 Compression
|
||||
/// NOTE: The Device Firmware converts a TEXT_MESSAGE_APP to TEXT_MESSAGE_COMPRESSED_APP if the compressed
|
||||
/// payload is shorter. There's no need for app developers to do this themselves. Also the firmware will decompress
|
||||
/// any incoming TEXT_MESSAGE_COMPRESSED_APP payload and convert to TEXT_MESSAGE_APP.
|
||||
case textMessageCompressedApp // = 7
|
||||
|
||||
///
|
||||
/// Waypoint payloads.
|
||||
/// Payload is a [Waypoint](/docs/developers/protobufs/api#waypoint) message
|
||||
/// ENCODING: Protobuf
|
||||
case waypointApp // = 8
|
||||
|
||||
///
|
||||
/// Audio Payloads.
|
||||
/// Encapsulated codec2 packets. On 2.4 GHZ Bandwidths only for now
|
||||
/// ENCODING: codec2 audio frames
|
||||
/// NOTE: audio frames contain a 3 byte header (0xc0 0xde 0xc2) and a one byte marker for the decompressed bitrate.
|
||||
/// This marker comes from the 'moduleConfig.audio.bitrate' enum minus one.
|
||||
case audioApp // = 9
|
||||
|
||||
///
|
||||
/// Same as Text Message but originating from Detection Sensor Module.
|
||||
case detectionSensorApp // = 10
|
||||
|
||||
///
|
||||
/// Provides a 'ping' service that replies to any packet it receives.
|
||||
/// Also serves as a small example module.
|
||||
/// ENCODING: ASCII Plaintext
|
||||
case replyApp // = 32
|
||||
|
||||
///
|
||||
/// Used for the python IP tunnel feature
|
||||
/// ENCODING: IP Packet. Handled by the python API, firmware ignores this one and pases on.
|
||||
case ipTunnelApp // = 33
|
||||
|
||||
///
|
||||
|
|
@ -102,26 +123,31 @@ enum PortNum: SwiftProtobuf.Enum {
|
|||
/// network is forwarded to the RX pin while sending a packet to TX will go out to the Mesh network.
|
||||
/// Maximum packet size of 240 bytes.
|
||||
/// Module is disabled by default can be turned on by setting SERIAL_MODULE_ENABLED = 1 in SerialPlugh.cpp.
|
||||
/// ENCODING: binary undefined
|
||||
case serialApp // = 64
|
||||
|
||||
///
|
||||
/// STORE_FORWARD_APP (Work in Progress)
|
||||
/// Maintained by Jm Casler (MC Hamster) : jm@casler.org
|
||||
/// ENCODING: Protobuf
|
||||
case storeForwardApp // = 65
|
||||
|
||||
///
|
||||
/// Optional port for messages for the range test module.
|
||||
/// ENCODING: ASCII Plaintext
|
||||
case rangeTestApp // = 66
|
||||
|
||||
///
|
||||
/// Provides a format to send and receive telemetry data from the Meshtastic network.
|
||||
/// Maintained by Charles Crossan (crossan007) : crossan007@gmail.com
|
||||
/// ENCODING: Protobuf
|
||||
case telemetryApp // = 67
|
||||
|
||||
///
|
||||
/// Experimental tools for estimating node position without a GPS
|
||||
/// Maintained by Github user a-f-G-U-C (a Meshtastic contributor)
|
||||
/// Project files at https://github.com/a-f-G-U-C/Meshtastic-ZPS
|
||||
/// ENCODING: arrays of int64 fields
|
||||
case zpsApp // = 68
|
||||
|
||||
///
|
||||
|
|
@ -129,15 +155,18 @@ enum PortNum: SwiftProtobuf.Enum {
|
|||
/// as if they did using their LoRa chip.
|
||||
/// Maintained by GitHub user GUVWAF.
|
||||
/// Project files at https://github.com/GUVWAF/Meshtasticator
|
||||
/// ENCODING: Protobuf (?)
|
||||
case simulatorApp // = 69
|
||||
|
||||
///
|
||||
/// Provides a traceroute functionality to show the route a packet towards
|
||||
/// a certain destination would take on the mesh.
|
||||
/// ENCODING: Protobuf
|
||||
case tracerouteApp // = 70
|
||||
|
||||
///
|
||||
/// Aggregates edge info for the network by sending out a list of each node's neighbors
|
||||
/// ENCODING: Protobuf
|
||||
case neighborinfoApp // = 71
|
||||
|
||||
///
|
||||
|
|
@ -148,6 +177,7 @@ enum PortNum: SwiftProtobuf.Enum {
|
|||
|
||||
///
|
||||
/// ATAK Forwarder Module https://github.com/paulmandal/atak-forwarder
|
||||
/// ENCODING: libcotshrink
|
||||
case atakForwarder // = 257
|
||||
|
||||
///
|
||||
|
|
@ -171,6 +201,7 @@ enum PortNum: SwiftProtobuf.Enum {
|
|||
case 7: self = .textMessageCompressedApp
|
||||
case 8: self = .waypointApp
|
||||
case 9: self = .audioApp
|
||||
case 10: self = .detectionSensorApp
|
||||
case 32: self = .replyApp
|
||||
case 33: self = .ipTunnelApp
|
||||
case 64: self = .serialApp
|
||||
|
|
@ -200,6 +231,7 @@ enum PortNum: SwiftProtobuf.Enum {
|
|||
case .textMessageCompressedApp: return 7
|
||||
case .waypointApp: return 8
|
||||
case .audioApp: return 9
|
||||
case .detectionSensorApp: return 10
|
||||
case .replyApp: return 32
|
||||
case .ipTunnelApp: return 33
|
||||
case .serialApp: return 64
|
||||
|
|
@ -234,6 +266,7 @@ extension PortNum: CaseIterable {
|
|||
.textMessageCompressedApp,
|
||||
.waypointApp,
|
||||
.audioApp,
|
||||
.detectionSensorApp,
|
||||
.replyApp,
|
||||
.ipTunnelApp,
|
||||
.serialApp,
|
||||
|
|
@ -270,6 +303,7 @@ extension PortNum: SwiftProtobuf._ProtoNameProviding {
|
|||
7: .same(proto: "TEXT_MESSAGE_COMPRESSED_APP"),
|
||||
8: .same(proto: "WAYPOINT_APP"),
|
||||
9: .same(proto: "AUDIO_APP"),
|
||||
10: .same(proto: "DETECTION_SENSOR_APP"),
|
||||
32: .same(proto: "REPLY_APP"),
|
||||
33: .same(proto: "IP_TUNNEL_APP"),
|
||||
64: .same(proto: "SERIAL_APP"),
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ struct Connect: View {
|
|||
if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == .connected {
|
||||
HStack {
|
||||
VStack(alignment: .center) {
|
||||
CircleText(text: node?.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node?.num ?? 0))), circleSize: 80, fontSize: (node?.user?.shortName ?? "???").isEmoji() ? 52 : 30, textColor: UIColor(hex: UInt32(node?.num ?? 0)).isLight() ? .black : .white )
|
||||
CircleText(text: node?.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node?.num ?? 0))), circleSize: 90, fontSize: (node?.user?.shortName ?? "???").isEmoji() ? 52 : (node?.user?.shortName?.count ?? 0 == 4 ? 26 : 36), textColor: UIColor(hex: UInt32(node?.num ?? 0)).isLight() ? .black : .white )
|
||||
}
|
||||
.padding(.trailing)
|
||||
VStack(alignment: .leading) {
|
||||
|
|
@ -89,22 +89,20 @@ struct Connect: View {
|
|||
|
||||
if node != nil {
|
||||
#if !targetEnvironment(macCatalyst)
|
||||
if #available(iOS 16.2, *) {
|
||||
Button {
|
||||
if !liveActivityStarted {
|
||||
Button {
|
||||
if !liveActivityStarted {
|
||||
#if canImport(ActivityKit)
|
||||
print("Start live activity.")
|
||||
startNodeActivity()
|
||||
#endif
|
||||
} else {
|
||||
#if canImport(ActivityKit)
|
||||
print("Start live activity.")
|
||||
startNodeActivity()
|
||||
#endif
|
||||
} else {
|
||||
#if canImport(ActivityKit)
|
||||
print("Stop live activity.")
|
||||
endActivity()
|
||||
#endif
|
||||
}
|
||||
} label: {
|
||||
Label("mesh.live.activity", systemImage: liveActivityStarted ? "stop" : "play")
|
||||
print("Stop live activity.")
|
||||
endActivity()
|
||||
#endif
|
||||
}
|
||||
} label: {
|
||||
Label("mesh.live.activity", systemImage: liveActivityStarted ? "stop" : "play")
|
||||
}
|
||||
#endif
|
||||
Text("Num: \(String(node!.num))")
|
||||
|
|
@ -251,7 +249,7 @@ struct Connect: View {
|
|||
.navigationTitle("bluetooth")
|
||||
.navigationBarItems(leading: MeshtasticLogo(), trailing:
|
||||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????", mqttProxyConnected: bleManager.mqttProxyConnected)
|
||||
})
|
||||
}
|
||||
.sheet(isPresented: $invalidFirmwareVersion, onDismiss: didDismissSheet) {
|
||||
|
|
@ -293,40 +291,36 @@ struct Connect: View {
|
|||
}
|
||||
#if canImport(ActivityKit)
|
||||
func startNodeActivity() {
|
||||
if #available(iOS 16.2, *) {
|
||||
liveActivityStarted = true
|
||||
let timerSeconds = 60
|
||||
|
||||
let deviceMetrics = node?.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0"))
|
||||
let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity
|
||||
liveActivityStarted = true
|
||||
let timerSeconds = 60
|
||||
|
||||
let deviceMetrics = node?.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0"))
|
||||
let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity
|
||||
|
||||
let activityAttributes = MeshActivityAttributes(nodeNum: Int(node?.num ?? 0), name: node?.user?.longName ?? "unknown")
|
||||
let activityAttributes = MeshActivityAttributes(nodeNum: Int(node?.num ?? 0), name: node?.user?.longName ?? "unknown")
|
||||
|
||||
let future = Date(timeIntervalSinceNow: Double(timerSeconds))
|
||||
let future = Date(timeIntervalSinceNow: Double(timerSeconds))
|
||||
|
||||
let initialContentState = MeshActivityAttributes.ContentState(timerRange: Date.now...future, connected: true, channelUtilization: mostRecent?.channelUtilization ?? 0.0, airtime: mostRecent?.airUtilTx ?? 0.0, batteryLevel: UInt32(mostRecent?.batteryLevel ?? 0))
|
||||
let initialContentState = MeshActivityAttributes.ContentState(timerRange: Date.now...future, connected: true, channelUtilization: mostRecent?.channelUtilization ?? 0.0, airtime: mostRecent?.airUtilTx ?? 0.0, batteryLevel: UInt32(mostRecent?.batteryLevel ?? 0))
|
||||
|
||||
let activityContent = ActivityContent(state: initialContentState, staleDate: Calendar.current.date(byAdding: .minute, value: 2, to: Date())!)
|
||||
let activityContent = ActivityContent(state: initialContentState, staleDate: Calendar.current.date(byAdding: .minute, value: 2, to: Date())!)
|
||||
|
||||
do {
|
||||
let myActivity = try Activity<MeshActivityAttributes>.request(attributes: activityAttributes, content: activityContent,
|
||||
pushType: nil)
|
||||
print(" Requested MyActivity live activity. ID: \(myActivity.id)")
|
||||
} catch let error {
|
||||
print("Error requesting live activity: \(error.localizedDescription)")
|
||||
}
|
||||
do {
|
||||
let myActivity = try Activity<MeshActivityAttributes>.request(attributes: activityAttributes, content: activityContent,
|
||||
pushType: nil)
|
||||
print(" Requested MyActivity live activity. ID: \(myActivity.id)")
|
||||
} catch let error {
|
||||
print("Error requesting live activity: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
func endActivity() {
|
||||
liveActivityStarted = false
|
||||
Task {
|
||||
if #available(iOS 16.2, *) {
|
||||
for activity in Activity<MeshActivityAttributes>.activities {
|
||||
// Check if this is the activity associated with this order.
|
||||
if activity.attributes.nodeNum == node?.num ?? 0 {
|
||||
await activity.end(nil, dismissalPolicy: .immediate)
|
||||
}
|
||||
for activity in Activity<MeshActivityAttributes>.activities {
|
||||
// Check if this is the activity associated with this order.
|
||||
if activity.attributes.nodeNum == node?.num ?? 0 {
|
||||
await activity.end(nil, dismissalPolicy: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,22 +5,11 @@
|
|||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
|
||||
@State private var selection: Tab = .ble
|
||||
|
||||
enum Tab {
|
||||
case contacts
|
||||
case messages
|
||||
case map
|
||||
case ble
|
||||
case nodes
|
||||
case settings
|
||||
}
|
||||
|
||||
@StateObject var appState = AppState.shared
|
||||
|
||||
var body: some View {
|
||||
|
||||
TabView(selection: $selection) {
|
||||
|
||||
|
||||
TabView(selection: $appState.tabSelection) {
|
||||
Contacts()
|
||||
.tabItem {
|
||||
Label("messages", systemImage: "message")
|
||||
|
|
@ -55,3 +44,12 @@ struct ContentView_Previews: PreviewProvider {
|
|||
ContentView()
|
||||
}
|
||||
}
|
||||
|
||||
enum Tab {
|
||||
case contacts
|
||||
case messages
|
||||
case map
|
||||
case ble
|
||||
case nodes
|
||||
case settings
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ struct CircleText: View {
|
|||
|
||||
struct CircleText_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
CircleText(text: "RDDN", color: Color.accentColor)
|
||||
CircleText(text: "MOMO", color: Color.accentColor)
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,12 +9,21 @@ struct ConnectedDevice: View {
|
|||
var bluetoothOn: Bool
|
||||
var deviceConnected: Bool
|
||||
var name: String
|
||||
var mqttProxyConnected: Bool = false
|
||||
|
||||
var body: some View {
|
||||
|
||||
HStack {
|
||||
|
||||
if bluetoothOn {
|
||||
if bluetoothOn {
|
||||
if deviceConnected && mqttProxyConnected {
|
||||
|
||||
if mqttProxyConnected {
|
||||
Image(systemName: "iphone.gen3.radiowaves.left.and.right.circle.fill")
|
||||
.imageScale(.large)
|
||||
.foregroundColor(.green)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
}
|
||||
if deviceConnected {
|
||||
Image(systemName: "antenna.radiowaves.left.and.right.circle.fill")
|
||||
.imageScale(.large)
|
||||
|
|
@ -27,7 +36,6 @@ struct ConnectedDevice: View {
|
|||
.imageScale(.medium)
|
||||
.foregroundColor(.red)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
|
||||
}
|
||||
} else {
|
||||
Text("bluetooth.off").font(.subheadline).foregroundColor(.red)
|
||||
|
|
@ -38,10 +46,10 @@ struct ConnectedDevice: View {
|
|||
|
||||
struct ConnectedDevice_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ConnectedDevice(bluetoothOn: true, deviceConnected: false, name: "Yellow Beam")
|
||||
ConnectedDevice(bluetoothOn: true, deviceConnected: true, name: "MEMO", mqttProxyConnected: true)
|
||||
.previewLayout(.fixed(width: 80, height: 70))
|
||||
|
||||
ConnectedDevice(bluetoothOn: true, deviceConnected: false, name: "Yellow Beam")
|
||||
ConnectedDevice(bluetoothOn: true, deviceConnected: false, name: "86D4", mqttProxyConnected: false)
|
||||
.previewLayout(.fixed(width: 80, height: 70))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -243,6 +243,19 @@ struct NodeInfoView: View {
|
|||
}
|
||||
Divider()
|
||||
}
|
||||
NavigationLink {
|
||||
DetectionSensorLog(node: node)
|
||||
} label: {
|
||||
|
||||
Image(systemName: "sensor")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(.title)
|
||||
|
||||
Text("Detection Sensor Log")
|
||||
.font(.title3)
|
||||
}
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,12 +63,24 @@ struct ChannelMessageList: View {
|
|||
VStack(alignment: currentUser ? .trailing : .leading) {
|
||||
let markdownText: LocalizedStringKey = LocalizedStringKey.init(message.messagePayloadMarkdown ?? (message.messagePayload ?? "EMPTY MESSAGE"))
|
||||
let linkBlue = Color(red: 0.4627, green: 0.8392, blue: 1) /* #76d6ff */
|
||||
let isDetectionSensorMessage = message.portNum == Int32(PortNum.detectionSensorApp.rawValue)
|
||||
|
||||
Text(markdownText)
|
||||
.tint(linkBlue)
|
||||
.padding(10)
|
||||
.foregroundColor(.white)
|
||||
.background(currentUser ? .accentColor : Color(.gray))
|
||||
.cornerRadius(15)
|
||||
.overlay(
|
||||
VStack {
|
||||
isDetectionSensorMessage ? Image(systemName: "sensor.fill")
|
||||
.padding()
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
|
||||
.foregroundStyle(Color.orange)
|
||||
.offset(x: 20, y: -20)
|
||||
: nil
|
||||
}
|
||||
)
|
||||
.contextMenu {
|
||||
VStack {
|
||||
Text("channel")+Text(": \(message.channel)")
|
||||
|
|
@ -185,6 +197,9 @@ struct ChannelMessageList: View {
|
|||
let ackErrorVal = RoutingError(rawValue: Int(message.ackError))
|
||||
Text("\(ackErrorVal?.display ?? "Empty Ack Error")").fixedSize(horizontal: false, vertical: true)
|
||||
.font(.caption2).foregroundColor(.red)
|
||||
} else if isDetectionSensorMessage {
|
||||
let messageDate = message.timestamp
|
||||
Text(" \(messageDate.formattedDate(format: dateFormatString))").font(.caption2).foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
157
Meshtastic/Views/Nodes/DetectionSensorLog.swift
Normal file
157
Meshtastic/Views/Nodes/DetectionSensorLog.swift
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
//
|
||||
// DetectionSensorLog.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Ben on 8/22/23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Charts
|
||||
|
||||
struct DetectionSensorLog: View {
|
||||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
|
||||
@State private var isPresentingClearLogConfirm: Bool = false
|
||||
@State var isExporting = false
|
||||
@State var exportString = ""
|
||||
|
||||
var node: NodeInfoEntity
|
||||
|
||||
var body: some View {
|
||||
|
||||
let oneDayAgo = Calendar.current.date(byAdding: .day, value: -1, to: Date())
|
||||
let detections = getDetectionSensorMessages(nodeNum: node.num, context: context)
|
||||
let chartData = detections
|
||||
.filter { $0.timestamp >= oneDayAgo! }
|
||||
.sorted { $0.timestamp < $1.timestamp }
|
||||
|
||||
NavigationStack {
|
||||
|
||||
if chartData.count > 0 {
|
||||
GroupBox(label: Label("\(detections.count) Total Detection Events", systemImage: "sensor")) {
|
||||
|
||||
Chart {
|
||||
ForEach(chartData, id: \.self) { point in
|
||||
Plot {
|
||||
BarMark(
|
||||
x: .value("x", point.timestamp),
|
||||
y: .value("y", 1)
|
||||
)
|
||||
}
|
||||
.accessibilityLabel("Bar Series")
|
||||
.accessibilityValue("X: \(point.timestamp), Y: \(1)")
|
||||
.interpolationMethod(.cardinal)
|
||||
.foregroundStyle(
|
||||
.linearGradient(
|
||||
colors: [.green, .yellow, .orange, .red],
|
||||
startPoint: .bottom,
|
||||
endPoint: .top
|
||||
)
|
||||
)
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}
|
||||
.chartXAxis(content: {
|
||||
AxisMarks(position: .top)
|
||||
// AxisMarks(position: .top, values: .stride(by: .hour)) { date in
|
||||
// AxisValueLabel(format: .dateTime.hour())
|
||||
// }
|
||||
})
|
||||
.chartXAxis(.automatic)
|
||||
.chartYScale(domain: 0...20)
|
||||
.chartForegroundStyleScale([
|
||||
"Detection events" : .green,
|
||||
])
|
||||
.chartLegend(position: .automatic, alignment: .bottom)
|
||||
}
|
||||
.frame(minHeight: 250)
|
||||
}
|
||||
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current)
|
||||
let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma").replacingOccurrences(of: ",", with: "")
|
||||
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||
|
||||
// Add a table for mac and ipad
|
||||
Table(detections) {
|
||||
TableColumn("Detection event") { d in
|
||||
Text(d.messagePayload ?? "Detected")
|
||||
}
|
||||
|
||||
TableColumn("timestamp") { d in
|
||||
Text(d.timestamp.formattedDate(format: dateFormatString))
|
||||
}
|
||||
.width(min: 180)
|
||||
}
|
||||
} else {
|
||||
ScrollView {
|
||||
let columns = [
|
||||
GridItem(),
|
||||
GridItem()
|
||||
]
|
||||
LazyVGrid(columns: columns, alignment: .leading, spacing: 1) {
|
||||
GridRow {
|
||||
Text("Detection")
|
||||
.font(.caption)
|
||||
.fontWeight(.bold)
|
||||
Text("timestamp")
|
||||
.font(.caption)
|
||||
.fontWeight(.bold)
|
||||
}
|
||||
ForEach(detections) { d in
|
||||
GridRow {
|
||||
Text(d.messagePayload ?? "Detected")
|
||||
Text(d.timestamp.formattedDate(format: dateFormatString))
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.leading, 15)
|
||||
.padding(.trailing, 5)
|
||||
}
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
Button {
|
||||
exportString = detectionsToCsv(detections: chartData)
|
||||
isExporting = true
|
||||
} label: {
|
||||
Label("save", systemImage: "square.and.arrow.down")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding(.bottom)
|
||||
.padding(.trailing)
|
||||
}
|
||||
.navigationTitle("detection.sensor.log")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
|
||||
})
|
||||
.onAppear {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
.fileExporter(
|
||||
isPresented: $isExporting,
|
||||
document: CsvDocument(emptyCsv: exportString),
|
||||
contentType: .commaSeparatedText,
|
||||
defaultFilename: String("\(node.user?.longName ?? "Node") \("detection.sensor.log".localized)"),
|
||||
onCompletion: { result in
|
||||
if case .success = result {
|
||||
print("Detections metrics log download succeeded.")
|
||||
self.isExporting = false
|
||||
} else {
|
||||
print("Detections log download failed: \(result).")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
//
|
||||
//struct DetectionSensorLog_Previews: PreviewProvider {
|
||||
// static var previews: some View {
|
||||
// DetectionSensorLog()
|
||||
// }
|
||||
//}
|
||||
|
|
@ -47,8 +47,7 @@ struct DeviceMetricsLog: View {
|
|||
.accessibilityLabel("Line Series")
|
||||
.accessibilityValue("X: \(point.time!), Y: \(point.batteryLevel)")
|
||||
.foregroundStyle(batteryChartColor)
|
||||
.interpolationMethod(.cardinal)
|
||||
//.interpolationMethod(.catmullRom(alpha: 1.0))
|
||||
.interpolationMethod(.catmullRom(alpha: 1.0))
|
||||
|
||||
Plot {
|
||||
PointMark(
|
||||
|
|
@ -181,7 +180,7 @@ struct DeviceMetricsLog: View {
|
|||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding(.bottom)
|
||||
.padding(.trailing)
|
||||
.padding(.leading)
|
||||
.confirmationDialog(
|
||||
"are.you.sure",
|
||||
isPresented: $isPresentingClearLogConfirm,
|
||||
|
|
@ -195,6 +194,7 @@ struct DeviceMetricsLog: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
exportString = telemetryToCsvFile(telemetry: deviceMetrics, metricsType: 0)
|
||||
isExporting = true
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ struct EnvironmentMetricsLog: View {
|
|||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding(.bottom)
|
||||
.padding(.trailing)
|
||||
.padding(.leading)
|
||||
.confirmationDialog(
|
||||
"are.you.sure",
|
||||
isPresented: $isPresentingClearLogConfirm,
|
||||
|
|
@ -191,7 +191,7 @@ struct EnvironmentMetricsLog: View {
|
|||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding(.bottom)
|
||||
.padding(.leading)
|
||||
.padding(.trailing)
|
||||
}
|
||||
.navigationTitle("Environment Metrics Log")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,16 @@ import SwiftUI
|
|||
import CoreLocation
|
||||
|
||||
struct NodeList: View {
|
||||
|
||||
@State private var searchText = ""
|
||||
var nodesQuery: Binding<String> {
|
||||
Binding {
|
||||
searchText
|
||||
} set: { newValue in
|
||||
searchText = newValue
|
||||
nodes.nsPredicate = newValue.isEmpty ? nil : NSPredicate(format: "user.longName CONTAINS[c] %@ OR user.shortName CONTAINS[c] %@", newValue, newValue)
|
||||
}
|
||||
}
|
||||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
|
|
@ -38,7 +48,7 @@ struct NodeList: View {
|
|||
LazyVStack(alignment: .leading) {
|
||||
HStack {
|
||||
VStack(alignment: .leading) {
|
||||
CircleText(text: node.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65, fontSize: (node.user?.shortName ?? "???").isEmoji() ? 44 : 22, brightness: 0.0, textColor: UIColor(hex: UInt32(node.num)).isLight() ? .black : .white)
|
||||
CircleText(text: node.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65, fontSize: (node.user?.shortName ?? "???").isEmoji() ? 44 : (node.user?.shortName?.count ?? 0 == 4 ? 19 : 26), brightness: 0.0, textColor: UIColor(hex: UInt32(node.num)).isLight() ? .black : .white)
|
||||
.padding(.trailing, 5)
|
||||
let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0"))
|
||||
if deviceMetrics?.count ?? 0 >= 1 {
|
||||
|
|
@ -116,5 +126,7 @@ struct NodeList: View {
|
|||
Text("select.node")
|
||||
}
|
||||
}
|
||||
.searchable(text: nodesQuery, prompt: "Find a node")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ struct NodeMap: View {
|
|||
@EnvironmentObject var bleManager: BLEManager
|
||||
|
||||
@ObservedObject var tileManager = OfflineTileManager.shared
|
||||
@StateObject var appState = AppState.shared
|
||||
|
||||
@State var selectedMapLayer: MapLayer = UserDefaults.mapLayer
|
||||
@State var enableMapRecentering: Bool = UserDefaults.enableMapRecentering
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ struct PositionLog: View {
|
|||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding(.bottom)
|
||||
.padding(.trailing)
|
||||
.padding(.leading)
|
||||
.confirmationDialog(
|
||||
"are.you.sure",
|
||||
isPresented: $isPresentingClearLogConfirm,
|
||||
|
|
@ -154,7 +154,7 @@ struct PositionLog: View {
|
|||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding(.bottom)
|
||||
.padding(.leading)
|
||||
.padding(.trailing)
|
||||
}
|
||||
.fileExporter(
|
||||
isPresented: $isExporting,
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ struct DeviceConfig: View {
|
|||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
.padding(.leading)
|
||||
.confirmationDialog(
|
||||
"are.you.sure",
|
||||
isPresented: $isPresentingNodeDBResetConfirm,
|
||||
|
|
@ -174,7 +174,7 @@ struct DeviceConfig: View {
|
|||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
.padding(.trailing)
|
||||
.confirmationDialog(
|
||||
"All device and app data will be deleted. You will also need to forget your devices under Settings > Bluetooth.",
|
||||
isPresented: $isPresentingFactoryResetConfirm,
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ struct LoRaConfig: View {
|
|||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Text("Sets the maximum number of hops, default is 3. Increasing hops also increases air time utilization and should be used carefully.")
|
||||
Text("Sets the maximum number of hops, default is 3. Increasing hops also increases congestion and should be used carefully.")
|
||||
.font(.caption)
|
||||
|
||||
HStack {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,243 @@
|
|||
//
|
||||
// DetectionSensorModule.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Copyright(c) Garth Vander Houwen 8/16/23.
|
||||
//
|
||||
import SwiftUI
|
||||
|
||||
struct DetectionSensorConfig: View {
|
||||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@Environment(\.dismiss) private var goBack
|
||||
var node: NodeInfoEntity?
|
||||
@State private var isPresentingSaveConfirm: Bool = false
|
||||
@State var hasChanges: Bool = false
|
||||
@State var enabled = false
|
||||
/// DetectionSensorModule will sends a bell character with the messages.
|
||||
@State var sendBell: Bool = false
|
||||
@State var name: String = ""
|
||||
@State var detectionTriggeredHigh: Bool = true
|
||||
@State var usePullup: Bool = false
|
||||
@State var minimumBroadcastSecs = 0
|
||||
@State var stateBroadcastSecs = 0
|
||||
@State var monitorPin = 0
|
||||
|
||||
var body: some View {
|
||||
|
||||
Form {
|
||||
if node != nil && node?.metadata == nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 {
|
||||
Text("There has been no response to a request for device metadata over the admin channel for this node.")
|
||||
.font(.callout)
|
||||
.foregroundColor(.orange)
|
||||
|
||||
} else if node != nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 {
|
||||
// Let users know what is going on if they are using remote admin and don't have the config yet
|
||||
if node?.detectionSensorConfig == nil {
|
||||
Text("Detection Sensor config data was requested over the admin channel but no response has been returned from the remote node. You can check the status of admin message requests in the admin message log.")
|
||||
.font(.callout)
|
||||
.foregroundColor(.orange)
|
||||
} else {
|
||||
Text("Remote administration for: \(node?.user?.longName ?? "Unknown")")
|
||||
.font(.title3)
|
||||
.onAppear {
|
||||
setDetectionSensorValues()
|
||||
}
|
||||
}
|
||||
} else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 {
|
||||
Text("Configuration for: \(node?.user?.longName ?? "Unknown")")
|
||||
.font(.title3)
|
||||
} else {
|
||||
Text("Please connect to a radio to configure settings.")
|
||||
.font(.callout)
|
||||
.foregroundColor(.orange)
|
||||
}
|
||||
Section(header: Text("options")) {
|
||||
Toggle(isOn: $enabled) {
|
||||
Label("enabled", systemImage: "dot.radiowaves.right")
|
||||
}
|
||||
Toggle(isOn: $sendBell) {
|
||||
Label("Send Bell", systemImage: "bell")
|
||||
}
|
||||
TextField("Friendly name (sent for detection alerts text messages)", text: $name, axis: .vertical)
|
||||
.foregroundColor(.gray)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.onChange(of: name, perform: { _ in
|
||||
|
||||
let totalBytes = name.utf8.count
|
||||
// Only mess with the value if it is too big
|
||||
if totalBytes > 20 {
|
||||
|
||||
let firstNBytes = Data(name.utf8.prefix(20))
|
||||
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
|
||||
// Set the shortName back to the last place where it was the right size
|
||||
name = maxBytesString
|
||||
}
|
||||
}
|
||||
})
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
Section(header: Text("Sensor option")) {
|
||||
Picker("GPIO Pin to monitor", selection: $monitorPin) {
|
||||
ForEach(0..<46) {
|
||||
if $0 == 0 {
|
||||
Text("unset")
|
||||
} else {
|
||||
Text("Pin \($0)")
|
||||
}
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Toggle(isOn: $detectionTriggeredHigh) {
|
||||
Label("Detection trigger High", systemImage: "dial.high")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
Toggle(isOn: $usePullup) {
|
||||
Label("Uses pullup resistor", systemImage: "arrow.up.to.line")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
|
||||
Section(header: Text("update.interval")) {
|
||||
Picker("Minimum time between detection broadcasts", selection: $minimumBroadcastSecs) {
|
||||
ForEach(UpdateIntervals.allCases) { ui in
|
||||
Text(ui.description).tag(ui.rawValue)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Text("Mininum time between detection broadcasts. Default is 45 seconds.")
|
||||
.font(.caption)
|
||||
|
||||
Picker("State Broadcast Interval", selection: $stateBroadcastSecs) {
|
||||
Text("Never").tag(0)
|
||||
ForEach(UpdateIntervals.allCases) { ui in
|
||||
Text(ui.description).tag(ui.rawValue)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Text("How often to send detection sensor state to mesh regardless of detection. Default is Never.")
|
||||
.font(.caption)
|
||||
|
||||
}
|
||||
}
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
.disabled(self.bleManager.connectedPeripheral == nil || node?.detectionSensorConfig == nil)
|
||||
|
||||
Button {
|
||||
isPresentingSaveConfirm = true
|
||||
} label: {
|
||||
Label("save", systemImage: "square.and.arrow.down")
|
||||
}
|
||||
.disabled(bleManager.connectedPeripheral == nil || !hasChanges)
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
.confirmationDialog(
|
||||
"are.you.sure",
|
||||
isPresented: $isPresentingSaveConfirm,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context)
|
||||
if connectedNode != nil {
|
||||
let nodeName = node?.user?.longName ?? "unknown".localized
|
||||
let buttonText = String.localizedStringWithFormat("save.config %@".localized, nodeName)
|
||||
Button(buttonText) {
|
||||
var dsc = ModuleConfig.DetectionSensorConfig()
|
||||
dsc.enabled = self.enabled
|
||||
dsc.sendBell = self.sendBell
|
||||
dsc.name = self.name
|
||||
dsc.monitorPin = UInt32(self.monitorPin)
|
||||
dsc.detectionTriggeredHigh = self.detectionTriggeredHigh
|
||||
dsc.usePullup = self.usePullup
|
||||
dsc.minimumBroadcastSecs = UInt32(self.minimumBroadcastSecs)
|
||||
dsc.stateBroadcastSecs = UInt32(self.stateBroadcastSecs)
|
||||
|
||||
let adminMessageId = bleManager.saveDetectionSensorModuleConfig(config: dsc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
if adminMessageId > 0 {
|
||||
// Should show a saved successfully alert once I know that to be true
|
||||
// for now just disable the button after a successful save
|
||||
hasChanges = false
|
||||
goBack()
|
||||
} }
|
||||
}
|
||||
}
|
||||
message: {
|
||||
Text("config.save.confirm")
|
||||
}
|
||||
.navigationTitle("detection.sensor.config")
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
|
||||
})
|
||||
.onAppear {
|
||||
self.bleManager.context = context
|
||||
setDetectionSensorValues()
|
||||
|
||||
// Need to request a Detection Sensor Module Config from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.detectionSensorConfig == nil {
|
||||
print("empty detection sensor module config")
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
|
||||
if node != nil && connectedNode != nil {
|
||||
_ = bleManager.requestDetectionSensorModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: enabled) { newEnabled in
|
||||
if node != nil && node?.detectionSensorConfig != nil {
|
||||
if newEnabled != node!.detectionSensorConfig!.enabled { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: sendBell) { newSendBell in
|
||||
if node != nil && node?.detectionSensorConfig != nil {
|
||||
if newSendBell != node!.detectionSensorConfig!.sendBell { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: detectionTriggeredHigh) { newDetectionTriggeredHigh in
|
||||
if node != nil && node?.detectionSensorConfig != nil {
|
||||
if newDetectionTriggeredHigh != node!.detectionSensorConfig!.detectionTriggeredHigh { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: usePullup) { newUsePullup in
|
||||
if node != nil && node?.detectionSensorConfig != nil {
|
||||
if newUsePullup != node!.detectionSensorConfig!.usePullup { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: name) { newName in
|
||||
if node != nil && node?.detectionSensorConfig != nil {
|
||||
if newName != node!.detectionSensorConfig!.name { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: monitorPin) { newMonitorPin in
|
||||
if node != nil && node?.detectionSensorConfig != nil {
|
||||
if newMonitorPin != node!.detectionSensorConfig!.monitorPin { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: minimumBroadcastSecs) { newMinimumBroadcastSecs in
|
||||
if node != nil && node?.detectionSensorConfig != nil {
|
||||
if newMinimumBroadcastSecs != node!.detectionSensorConfig!.minimumBroadcastSecs { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: stateBroadcastSecs) { newStateBroadcastSecs in
|
||||
if node != nil && node?.detectionSensorConfig != nil {
|
||||
if newStateBroadcastSecs != node!.detectionSensorConfig!.stateBroadcastSecs { hasChanges = true }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setDetectionSensorValues() {
|
||||
self.enabled = (node?.detectionSensorConfig?.enabled ?? false)
|
||||
self.sendBell = (node?.detectionSensorConfig?.sendBell ?? false)
|
||||
self.name = (node?.detectionSensorConfig?.name ?? "")
|
||||
self.monitorPin = Int(node?.detectionSensorConfig?.monitorPin ?? 0)
|
||||
self.usePullup = (node?.detectionSensorConfig?.usePullup ?? false)
|
||||
self.detectionTriggeredHigh = (node?.detectionSensorConfig?.detectionTriggeredHigh ?? true)
|
||||
self.minimumBroadcastSecs = Int(node?.detectionSensorConfig?.minimumBroadcastSecs ?? 45)
|
||||
self.stateBroadcastSecs = Int(node?.detectionSensorConfig?.stateBroadcastSecs ?? 0)
|
||||
|
||||
self.hasChanges = false
|
||||
}
|
||||
}
|
||||
|
|
@ -65,6 +65,8 @@ struct MQTTConfig: View {
|
|||
Label("mqtt.clientproxy", systemImage: "iphone.radiowaves.left.and.right")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Text("If both MQTT and the client proxy are enabled your device will utalize an available network connection to connect to the specified MQTT server.")
|
||||
.font(.caption2)
|
||||
|
||||
Toggle(isOn: $encryptionEnabled) {
|
||||
|
||||
|
|
@ -82,8 +84,6 @@ struct MQTTConfig: View {
|
|||
Label("JSON Enabled", systemImage: "ellipsis.curlybraces")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Text("JSON mode is not reccomended it is incomplete and unstable.")
|
||||
.font(.caption2)
|
||||
}
|
||||
Section(header: Text("Custom Server")) {
|
||||
HStack {
|
||||
|
|
@ -187,7 +187,7 @@ struct MQTTConfig: View {
|
|||
Text("The root topic to use for MQTT messages. Default is \"msh\". This is useful if you want to use a single MQTT server for multiple meshtastic networks and separate them via ACLs")
|
||||
.font(.caption2)
|
||||
}
|
||||
Text("WiFi or Ethernet must also be enabled for MQTT to work. You can set uplink and downlink for each channel.")
|
||||
Text("You can set uplink and downlink for each channel.")
|
||||
.font(.callout)
|
||||
}
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
|
|
|
|||
|
|
@ -72,13 +72,13 @@ struct RangeTestConfig: View {
|
|||
.font(.caption)
|
||||
}
|
||||
}
|
||||
.disabled(self.bleManager.connectedPeripheral == nil || node?.rangeTestConfig == nil || !(node != nil && node?.metadata?.hasWifi ?? false))
|
||||
.disabled(self.bleManager.connectedPeripheral == nil || node?.rangeTestConfig == nil)
|
||||
Button {
|
||||
isPresentingSaveConfirm = true
|
||||
} label: {
|
||||
Label("save", systemImage: "square.and.arrow.down")
|
||||
}
|
||||
.disabled(bleManager.connectedPeripheral == nil || !hasChanges || !(node?.metadata?.hasWifi ?? false))
|
||||
.disabled(bleManager.connectedPeripheral == nil || !hasChanges)
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
|
|
|
|||
|
|
@ -67,23 +67,24 @@ struct MeshLog: View {
|
|||
print(error)
|
||||
}
|
||||
} label: {
|
||||
Label("Clear Log", systemImage: "trash.fill")
|
||||
Label("Clear", systemImage: "trash.fill")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
Spacer()
|
||||
.padding(.bottom)
|
||||
.padding(.leading)
|
||||
|
||||
Button {
|
||||
isExporting = true
|
||||
} label: {
|
||||
Label("Save Log", systemImage: "square.and.arrow.down")
|
||||
Label("Save", systemImage: "square.and.arrow.down")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
.padding(.bottom)
|
||||
.padding(.trailing)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.bottom, 10)
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ struct Settings: View {
|
|||
case networkConfig
|
||||
case positionConfig
|
||||
case cannedMessagesConfig
|
||||
case detectionSensorConfig
|
||||
case externalNotificationConfig
|
||||
case mqttConfig
|
||||
case rangeTestConfig
|
||||
|
|
@ -203,6 +204,17 @@ struct Settings: View {
|
|||
}
|
||||
.tag(SettingsSidebar.cannedMessagesConfig)
|
||||
|
||||
NavigationLink {
|
||||
DetectionSensorConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
|
||||
Image(systemName: "sensor")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
|
||||
Text("detection.sensor")
|
||||
}
|
||||
.tag(SettingsSidebar.detectionSensorConfig)
|
||||
|
||||
NavigationLink {
|
||||
ExternalNotificationConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
import WidgetKit
|
||||
import SwiftUI
|
||||
|
||||
@available(iOSApplicationExtension 16.2, *)
|
||||
@main
|
||||
struct WidgetsBundle: WidgetBundle {
|
||||
var body: some Widget {
|
||||
|
|
|
|||
|
|
@ -194,6 +194,7 @@
|
|||
"name"="Name";
|
||||
"network"="Netzwerk";
|
||||
"network.config"="Netzwerkeinstellungen";
|
||||
"nodes"="Nodes";
|
||||
"nodes %@"="Nodes (%@)";
|
||||
"no.nodes"="Keine Meshtastic Nodes gefunden";
|
||||
"not.connected"="Kein Gerät verbunden";
|
||||
|
|
|
|||
|
|
@ -57,6 +57,9 @@
|
|||
"current"="Current";
|
||||
"default"="Default";
|
||||
"delete"="Delete";
|
||||
"detection.sensor"="Detection Sensor";
|
||||
"detection.sensor.config"="Detection Sensor Config";
|
||||
"detection.sensor.log"="Detection Sensor Log";
|
||||
"device"="Device";
|
||||
"device.config"="Device Config";
|
||||
"device.metrics.delete"="Delete all device metrics?";
|
||||
|
|
@ -194,6 +197,7 @@
|
|||
"name"="Name";
|
||||
"network"="Network";
|
||||
"network.config"="Network Config";
|
||||
"nodes"="Nodes";
|
||||
"nodes %@"="Nodes (%@)";
|
||||
"no.nodes"="No Meshtastic Nodes Found";
|
||||
"not.connected"="No device connected";
|
||||
|
|
|
|||
4
thebenternify.sh
Executable file
4
thebenternify.sh
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
|
||||
sed -i '' -e 's/GCH7VS5Y9R/6YF6QJH524/g' ./Meshtastic.xcodeproj/project.pbxproj
|
||||
sed -i '' -e 's/gvh.Meshtastic/thebentern.Meshtastic/g' ./Meshtastic.xcodeproj/project.pbxproj
|
||||
4
unthebenternify.sh
Executable file
4
unthebenternify.sh
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
|
||||
sed -i '' -e 's/6YF6QJH524/GCH7VS5Y9R/g' ./Meshtastic.xcodeproj/project.pbxproj
|
||||
sed -i '' -e 's/thebentern.Meshtastic/gvh.Meshtastic/g' ./Meshtastic.xcodeproj/project.pbxproj
|
||||
|
|
@ -194,6 +194,7 @@
|
|||
"name"="名称";
|
||||
"network"="网络";
|
||||
"network.config"="网络配置";
|
||||
"nodes"="节点";
|
||||
"nodes %@"="节点 (%@)";
|
||||
"no.nodes"="未找到 Meshtastic 节点";
|
||||
"not.connected"="未连接到电台";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue