mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Merge pull request #624 from meshtastic/2.3.7_Working_Changes
2.3.7 working changes
This commit is contained in:
commit
ee22af34d6
26 changed files with 1108 additions and 290 deletions
|
|
@ -50,6 +50,7 @@
|
|||
DD2AD8A8296D2DF9001FF0E7 /* MapViewSwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */; };
|
||||
DD33DB622B3D27C7003E1EA0 /* FirmwareApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD33DB612B3D27C7003E1EA0 /* FirmwareApi.swift */; };
|
||||
DD3501892852FC3B000FC853 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3501882852FC3B000FC853 /* Settings.swift */; };
|
||||
DD354FD92BD96A0B0061A25F /* IAQScale.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD354FD82BD96A0B0061A25F /* IAQScale.swift */; };
|
||||
DD3619152B1EF9F900C41C8C /* LocationsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3619142B1EF9F900C41C8C /* LocationsHandler.swift */; };
|
||||
DD3CC6B528E33FD100FA9159 /* ShareChannels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6B428E33FD100FA9159 /* ShareChannels.swift */; };
|
||||
DD3CC6BC28E366DF00FA9159 /* Meshtastic.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */; };
|
||||
|
|
@ -87,7 +88,7 @@
|
|||
DD5E5211298EE33B00D21B61 /* remote_hardware.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E51FF298EE33B00D21B61 /* remote_hardware.pb.swift */; };
|
||||
DD5E5212298EE33B00D21B61 /* apponly.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E5200298EE33B00D21B61 /* apponly.pb.swift */; };
|
||||
DD5E5213298EE33B00D21B61 /* deviceonly.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E5201298EE33B00D21B61 /* deviceonly.pb.swift */; };
|
||||
DD5E523F298F5A9E00D21B61 /* AirQualityIndexCompact.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E523E298F5A9E00D21B61 /* AirQualityIndexCompact.swift */; };
|
||||
DD5E523F298F5A9E00D21B61 /* AirQualityIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E523E298F5A9E00D21B61 /* AirQualityIndex.swift */; };
|
||||
DD6193752862F6E600E59241 /* ExternalNotificationConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */; };
|
||||
DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */; };
|
||||
DD6193792863875F00E59241 /* SerialConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193782863875F00E59241 /* SerialConfig.swift */; };
|
||||
|
|
@ -292,10 +293,12 @@
|
|||
DD295CE92B323ED9002CC4AC /* MeshtasticDataModelV22.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV22.xcdatamodel; sourceTree = "<group>"; };
|
||||
DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewSwiftUI.swift; sourceTree = "<group>"; };
|
||||
DD2CC2E52ABFE04E00EDFDA7 /* MeshtasticDataModelV19.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV19.xcdatamodel; sourceTree = "<group>"; };
|
||||
DD31B04D2BDC6FD30024FA63 /* MeshtasticDataModelV 36.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 36.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DD31EC492B7F18B7006A3995 /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
DD33DB602B3D1ECC003E1EA0 /* MeshtasticDataModelV 23.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 23.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DD33DB612B3D27C7003E1EA0 /* FirmwareApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirmwareApi.swift; sourceTree = "<group>"; };
|
||||
DD3501882852FC3B000FC853 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; };
|
||||
DD354FD82BD96A0B0061A25F /* IAQScale.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IAQScale.swift; sourceTree = "<group>"; };
|
||||
DD3619132B1EE20700C41C8C /* MeshtasticDataModelV21.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV21.xcdatamodel; sourceTree = "<group>"; };
|
||||
DD3619142B1EF9F900C41C8C /* LocationsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationsHandler.swift; sourceTree = "<group>"; };
|
||||
DD398EBD2B93F640002B4C51 /* MeshtasticDataModelV 29.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 29.xcdatamodel"; sourceTree = "<group>"; };
|
||||
|
|
@ -339,7 +342,7 @@
|
|||
DD5E51FF298EE33B00D21B61 /* remote_hardware.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = remote_hardware.pb.swift; sourceTree = "<group>"; };
|
||||
DD5E5200298EE33B00D21B61 /* apponly.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = apponly.pb.swift; sourceTree = "<group>"; };
|
||||
DD5E5201298EE33B00D21B61 /* deviceonly.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = deviceonly.pb.swift; sourceTree = "<group>"; };
|
||||
DD5E523E298F5A9E00D21B61 /* AirQualityIndexCompact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirQualityIndexCompact.swift; sourceTree = "<group>"; };
|
||||
DD5E523E298F5A9E00D21B61 /* AirQualityIndex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirQualityIndex.swift; sourceTree = "<group>"; };
|
||||
DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalNotificationConfig.swift; sourceTree = "<group>"; };
|
||||
DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CannedMessagesConfig.swift; sourceTree = "<group>"; };
|
||||
DD6193782863875F00E59241 /* SerialConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialConfig.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -649,9 +652,11 @@
|
|||
DD5E523D298F5A7D00D21B61 /* Weather */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DD5E523E298F5A9E00D21B61 /* AirQualityIndexCompact.swift */,
|
||||
DD5E523E298F5A9E00D21B61 /* AirQualityIndex.swift */,
|
||||
DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */,
|
||||
DDA9515D2BC6F56F00CEA535 /* IndoorAirQuality.swift */,
|
||||
DD41A61429AB0035003C5A37 /* NodeWeatherForecast.swift */,
|
||||
DD354FD82BD96A0B0061A25F /* IAQScale.swift */,
|
||||
);
|
||||
path = Weather;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -921,7 +926,6 @@
|
|||
DDB75A1D2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift */,
|
||||
DDB75A202A12B954006ED576 /* LoRaSignalStrength.swift */,
|
||||
DDB75A222A13CDA9006ED576 /* BatteryLevelCompact.swift */,
|
||||
DDA9515D2BC6F56F00CEA535 /* IndoorAirQuality.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -1204,7 +1208,7 @@
|
|||
DD836AE726F6B38600ABCC23 /* Connect.swift in Sources */,
|
||||
DD0E20FD2B87090400F2D100 /* clientonly.pb.swift in Sources */,
|
||||
D93069082B81DF040066FBC8 /* SaveConfigButton.swift in Sources */,
|
||||
DD5E523F298F5A9E00D21B61 /* AirQualityIndexCompact.swift in Sources */,
|
||||
DD5E523F298F5A9E00D21B61 /* AirQualityIndex.swift in Sources */,
|
||||
DD964FBF296E76EF007C176F /* WaypointFormMapKit.swift in Sources */,
|
||||
DD3501892852FC3B000FC853 /* Settings.swift in Sources */,
|
||||
DDDC22382BA92344002C44F1 /* MeshMapContent.swift in Sources */,
|
||||
|
|
@ -1239,6 +1243,7 @@
|
|||
DD964FBD296E6B01007C176F /* EmojiOnlyTextField.swift in Sources */,
|
||||
DD8169FF272476C700F4AB02 /* LogDocument.swift in Sources */,
|
||||
DDC94FC129CE063B0082EA6E /* BatteryLevel.swift in Sources */,
|
||||
DD354FD92BD96A0B0061A25F /* IAQScale.swift in Sources */,
|
||||
DDDB445429F8AD1600EE2349 /* Data.swift in Sources */,
|
||||
DDDB26462AACC0B7003AFCB7 /* NodeInfoItem.swift in Sources */,
|
||||
DD2AD8A8296D2DF9001FF0E7 /* MapViewSwiftUI.swift in Sources */,
|
||||
|
|
@ -1578,7 +1583,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.3.6;
|
||||
MARKETING_VERSION = 2.3.7;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1612,7 +1617,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.3.6;
|
||||
MARKETING_VERSION = 2.3.7;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1685,7 +1690,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.3.6;
|
||||
MARKETING_VERSION = 2.3.7;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1718,7 +1723,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.3.6;
|
||||
MARKETING_VERSION = 2.3.7;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1820,6 +1825,7 @@
|
|||
DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
DD31B04D2BDC6FD30024FA63 /* MeshtasticDataModelV 36.xcdatamodel */,
|
||||
DD268D8C2BCC7D11008073AE /* MeshtasticDataModelV 35.xcdatamodel */,
|
||||
DDDBC87C2BC65682001E8DF7 /* MeshtasticDataModelV 34.xcdatamodel */,
|
||||
DDF45C382BC46B16005ED5F2 /* MeshtasticDataModelV33.xcdatamodel */,
|
||||
|
|
@ -1856,7 +1862,7 @@
|
|||
DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */,
|
||||
DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */,
|
||||
);
|
||||
currentVersion = DD268D8C2BCC7D11008073AE /* MeshtasticDataModelV 35.xcdatamodel */;
|
||||
currentVersion = DD31B04D2BDC6FD30024FA63 /* MeshtasticDataModelV 36.xcdatamodel */;
|
||||
name = Meshtastic.xcdatamodeld;
|
||||
path = Meshtastic/Meshtastic.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
|
|
|
|||
|
|
@ -8,6 +8,86 @@
|
|||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
enum Aqi: Int, CaseIterable, Identifiable {
|
||||
case good = 0
|
||||
case moderate = 1
|
||||
case sensitive = 2
|
||||
case unhealthy = 3
|
||||
case veryUnhealthy = 4
|
||||
case hazardous = 5
|
||||
|
||||
var id: Int { self.rawValue }
|
||||
var description: String {
|
||||
switch self {
|
||||
case .good:
|
||||
return "Good"
|
||||
case .moderate:
|
||||
return "Moderate"
|
||||
case .sensitive:
|
||||
return "Unhealthy for Sensitive Groups"
|
||||
case .unhealthy:
|
||||
return "Unhealthy"
|
||||
case .veryUnhealthy:
|
||||
return "Very Unhealthy"
|
||||
case .hazardous:
|
||||
return "Hazardous"
|
||||
}
|
||||
}
|
||||
var color: Color {
|
||||
switch self {
|
||||
case .good:
|
||||
return .green
|
||||
case .moderate:
|
||||
return .yellow
|
||||
case .sensitive:
|
||||
return .orange
|
||||
case .unhealthy:
|
||||
return .red
|
||||
case .veryUnhealthy:
|
||||
return .purple
|
||||
case .hazardous:
|
||||
return .magenta
|
||||
}
|
||||
}
|
||||
var range: Range<Int> {
|
||||
switch self {
|
||||
case .good:
|
||||
return Range(0...50)
|
||||
case .moderate:
|
||||
return Range(51...100)
|
||||
case .sensitive:
|
||||
return Range(101...150)
|
||||
case .unhealthy:
|
||||
return Range(151...200)
|
||||
case .veryUnhealthy:
|
||||
return Range(201...300)
|
||||
case .hazardous:
|
||||
return Range(301...500)
|
||||
}
|
||||
}
|
||||
|
||||
static func getAqi(for value: Int) -> Aqi {
|
||||
let aqi: Aqi
|
||||
switch value {
|
||||
case 0...50:
|
||||
aqi = .good
|
||||
case 51...100:
|
||||
aqi = .moderate
|
||||
case 101...150:
|
||||
aqi = .sensitive
|
||||
case 151...200:
|
||||
aqi = .unhealthy
|
||||
case 201...300:
|
||||
aqi = .veryUnhealthy
|
||||
case 301...500:
|
||||
aqi = .hazardous
|
||||
default:
|
||||
fatalError("Invalid int value")
|
||||
}
|
||||
return aqi
|
||||
}
|
||||
}
|
||||
|
||||
enum Iaq: Int, CaseIterable, Identifiable {
|
||||
case excellent = 0
|
||||
case good = 1
|
||||
|
|
@ -27,7 +107,7 @@ enum Iaq: Int, CaseIterable, Identifiable {
|
|||
case .lightlyPolluted:
|
||||
return "Lightly Polluted"
|
||||
case .moderatelyPolluted:
|
||||
return "Lightly Polluted"
|
||||
return "Moderately Polluted"
|
||||
case .heavilyPolluted:
|
||||
return "Heavily Polluted"
|
||||
case .severelyPolluted:
|
||||
|
|
@ -49,11 +129,30 @@ enum Iaq: Int, CaseIterable, Identifiable {
|
|||
case .heavilyPolluted:
|
||||
return .red
|
||||
case .severelyPolluted:
|
||||
return .purple
|
||||
return .magenta
|
||||
case .extremelyPolluted:
|
||||
return .brown
|
||||
}
|
||||
}
|
||||
|
||||
var range: Range<Int> {
|
||||
switch self {
|
||||
case .excellent:
|
||||
return Range(0...50)
|
||||
case .good:
|
||||
return Range(51...100)
|
||||
case .lightlyPolluted:
|
||||
return Range(101...150)
|
||||
case .moderatelyPolluted:
|
||||
return Range(151...200)
|
||||
case .heavilyPolluted:
|
||||
return Range(201...250)
|
||||
case .severelyPolluted:
|
||||
return Range(251...350)
|
||||
case .extremelyPolluted:
|
||||
return Range(351...500)
|
||||
}
|
||||
}
|
||||
static func getIaq(for value: Int) -> Iaq {
|
||||
let iaq: Iaq
|
||||
switch value {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ func telemetryToCsvFile(telemetry: [TelemetryEntity], metricsType: Int) -> Strin
|
|||
}
|
||||
} else if metricsType == 1 {
|
||||
// Create Environment Telemetry Header
|
||||
csvString = "Temperature, Relative Humidity, Barometric Pressure, Indoor Air Quality, Gas Resistance, \("voltage".localized), \("current".localized), \("timestamp".localized)"
|
||||
csvString = "Temperature, Relative Humidity, Barometric Pressure, Indoor Air Quality, Gas Resistance, \("timestamp".localized)"
|
||||
for dm in telemetry {
|
||||
if dm.metricsType == 1 {
|
||||
csvString += "\n"
|
||||
|
|
@ -46,10 +46,6 @@ func telemetryToCsvFile(telemetry: [TelemetryEntity], metricsType: Int) -> Strin
|
|||
csvString += ", "
|
||||
csvString += String(dm.gasResistance)
|
||||
csvString += ", "
|
||||
csvString += String(dm.voltage)
|
||||
csvString += ", "
|
||||
csvString += String(dm.current)
|
||||
csvString += ", "
|
||||
csvString += dm.time?.formattedDate(format: dateFormatString) ?? "unknown.age".localized
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ extension Color {
|
|||
let brightness = ((components[0] * 299) + (components[1] * 587) + (components[2] * 114)) / 1000
|
||||
return (brightness > 0.5)
|
||||
}
|
||||
public static let magenta = Color(red: 0.50, green: 0.00, blue: 0.00)
|
||||
}
|
||||
|
||||
extension UIColor {
|
||||
|
|
|
|||
|
|
@ -18,4 +18,14 @@ extension ChannelEntity {
|
|||
let unreadMessages = allPrivateMessages.filter{ ($0 as AnyObject).read == false }
|
||||
return unreadMessages.count
|
||||
}
|
||||
|
||||
var protoBuf: Channel {
|
||||
var channel = Channel()
|
||||
channel.index = self.index
|
||||
channel.settings.name = self.name ?? ""
|
||||
channel.settings.psk = self.psk ?? Data()
|
||||
channel.role = Channel.Role(rawValue: Int(self.role)) ?? Channel.Role.secondary
|
||||
channel.settings.moduleSettings.positionPrecision = UInt32(self.positionPrecision)
|
||||
return channel
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ extension UserDefaults {
|
|||
case enableSmartPosition
|
||||
case newNodeNotifications
|
||||
case lowBatteryNotifications
|
||||
case channelMessageNotifications
|
||||
case modemPreset
|
||||
case firmwareVersion
|
||||
case testIntEnum
|
||||
|
|
@ -146,6 +147,9 @@ extension UserDefaults {
|
|||
@UserDefault(.enableSmartPosition, defaultValue: false)
|
||||
static var enableSmartPosition: Bool
|
||||
|
||||
@UserDefault(.channelMessageNotifications, defaultValue: true)
|
||||
static var channelMessageNotifications: Bool
|
||||
|
||||
@UserDefault(.newNodeNotifications, defaultValue: true)
|
||||
static var newNodeNotifications: Bool
|
||||
|
||||
|
|
|
|||
|
|
@ -151,12 +151,15 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
isConnecting = false
|
||||
isConnected = false
|
||||
isSubscribed = false
|
||||
self.connectedPeripheral = nil
|
||||
invalidVersion = false
|
||||
connectedVersion = "0.0.0"
|
||||
connectedPeripheral = nil
|
||||
if timeoutTimer != nil {
|
||||
timeoutTimer!.invalidate()
|
||||
}
|
||||
automaticallyReconnect = false
|
||||
stopScanning()
|
||||
startScanning()
|
||||
}
|
||||
|
||||
|
|
@ -556,7 +559,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
tryClearExistingChannels()
|
||||
}
|
||||
// NodeInfo
|
||||
if decodedInfo.nodeInfo.num > 0 {// && !invalidVersion {
|
||||
if context != nil && decodedInfo.nodeInfo.num > 0 {// && !invalidVersion {
|
||||
nowKnown = true
|
||||
let nodeInfo = nodeInfoPacket(nodeInfo: decodedInfo.nodeInfo, channel: decodedInfo.packet.channel, context: context!)
|
||||
|
||||
|
|
@ -570,17 +573,17 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
}
|
||||
}
|
||||
// Channels
|
||||
if decodedInfo.channel.isInitialized && connectedPeripheral != nil {
|
||||
if context != nil && decodedInfo.channel.isInitialized && connectedPeripheral != nil {
|
||||
nowKnown = true
|
||||
channelPacket(channel: decodedInfo.channel, fromNum: Int64(truncatingIfNeeded: connectedPeripheral.num), context: context!)
|
||||
}
|
||||
// Config
|
||||
if decodedInfo.config.isInitialized && !invalidVersion && connectedPeripheral != nil {
|
||||
if context != nil && decodedInfo.config.isInitialized && !invalidVersion && connectedPeripheral != nil {
|
||||
nowKnown = true
|
||||
localConfig(config: decodedInfo.config, context: context!, nodeNum: Int64(truncatingIfNeeded: self.connectedPeripheral.num), nodeLongName: self.connectedPeripheral.longName)
|
||||
}
|
||||
// Module Config
|
||||
if decodedInfo.moduleConfig.isInitialized && !invalidVersion && self.connectedPeripheral?.num != 0{
|
||||
if context != nil && decodedInfo.moduleConfig.isInitialized && !invalidVersion && self.connectedPeripheral?.num != 0{
|
||||
nowKnown = true
|
||||
moduleConfig(config: decodedInfo.moduleConfig, context: context!, nodeNum: Int64(truncatingIfNeeded: self.connectedPeripheral?.num ?? 0), nodeLongName: self.connectedPeripheral.longName)
|
||||
if decodedInfo.moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.cannedMessage(decodedInfo.moduleConfig.cannedMessage) {
|
||||
|
|
@ -590,7 +593,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
}
|
||||
}
|
||||
// Device Metadata
|
||||
if decodedInfo.metadata.firmwareVersion.count > 0 && !invalidVersion {
|
||||
if context != nil && decodedInfo.metadata.firmwareVersion.count > 0 && !invalidVersion {
|
||||
nowKnown = true
|
||||
deviceMetadataPacket(metadata: decodedInfo.metadata, fromNum: connectedPeripheral.num, context: context!)
|
||||
connectedPeripheral.firmwareVersion = decodedInfo.metadata.firmwareVersion
|
||||
|
|
@ -994,37 +997,28 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
return success
|
||||
}
|
||||
|
||||
public func getPositionFromPhoneGPS(channel: Int32, destNum: Int64) -> Position? {
|
||||
public func getPositionFromPhoneGPS(destNum: Int64) -> Position? {
|
||||
var positionPacket = Position()
|
||||
|
||||
let fetchChannelRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "ChannelEntity")
|
||||
fetchChannelRequest.predicate = NSPredicate(format: "index == %lld", channel)
|
||||
|
||||
do {
|
||||
guard let fetchedChannel = try context!.fetch(fetchChannelRequest) as? [ChannelEntity] else {
|
||||
return nil
|
||||
}
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
|
||||
if let lastLocation = LocationsHandler.shared.locationsArray.last {
|
||||
|
||||
if fetchedChannel.count > 0 {
|
||||
positionPacket.latitudeI = Int32(lastLocation.coordinate.latitude * 1e7)
|
||||
positionPacket.longitudeI = Int32(lastLocation.coordinate.longitude * 1e7)
|
||||
let timestamp = lastLocation.timestamp
|
||||
positionPacket.time = UInt32(timestamp.timeIntervalSince1970)
|
||||
positionPacket.timestamp = UInt32(timestamp.timeIntervalSince1970)
|
||||
positionPacket.altitude = Int32(lastLocation.altitude)
|
||||
positionPacket.satsInView = UInt32(LocationsHandler.satsInView)
|
||||
positionPacket.precisionBits = UInt32(fetchedChannel[0].positionPrecision)
|
||||
let currentSpeed = lastLocation.speed
|
||||
if currentSpeed > 0 && (!currentSpeed.isNaN || !currentSpeed.isInfinite) {
|
||||
positionPacket.groundSpeed = UInt32(currentSpeed * 3.6)
|
||||
}
|
||||
let currentHeading = lastLocation.course
|
||||
if currentHeading > 0 && (!currentHeading.isNaN || !currentHeading.isInfinite) {
|
||||
positionPacket.groundTrack = UInt32(currentHeading)
|
||||
}
|
||||
positionPacket.latitudeI = Int32(lastLocation.coordinate.latitude * 1e7)
|
||||
positionPacket.longitudeI = Int32(lastLocation.coordinate.longitude * 1e7)
|
||||
let timestamp = lastLocation.timestamp
|
||||
positionPacket.time = UInt32(timestamp.timeIntervalSince1970)
|
||||
positionPacket.timestamp = UInt32(timestamp.timeIntervalSince1970)
|
||||
positionPacket.altitude = Int32(lastLocation.altitude)
|
||||
positionPacket.satsInView = UInt32(LocationsHandler.satsInView)
|
||||
|
||||
let currentSpeed = lastLocation.speed
|
||||
if currentSpeed > 0 && (!currentSpeed.isNaN || !currentSpeed.isInfinite) {
|
||||
positionPacket.groundSpeed = UInt32(currentSpeed * 3.6)
|
||||
}
|
||||
let currentHeading = lastLocation.course
|
||||
if currentHeading > 0 && (!currentHeading.isNaN || !currentHeading.isInfinite) {
|
||||
positionPacket.groundTrack = UInt32(currentHeading)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1032,9 +1026,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
if destNum <= 0 || LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) == 0.0 {
|
||||
return nil
|
||||
}
|
||||
if fetchedChannel.count <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
positionPacket.latitudeI = Int32(LocationHelper.currentLocation.latitude * 1e7)
|
||||
positionPacket.longitudeI = Int32(LocationHelper.currentLocation.longitude * 1e7)
|
||||
|
|
@ -1043,7 +1034,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
positionPacket.timestamp = UInt32(timestamp.timeIntervalSince1970)
|
||||
positionPacket.altitude = Int32(LocationHelper.shared.locationManager.location?.altitude ?? 0)
|
||||
positionPacket.satsInView = UInt32(LocationHelper.satsInView)
|
||||
positionPacket.precisionBits = UInt32(fetchedChannel[0].positionPrecision)
|
||||
let currentSpeed = LocationHelper.shared.locationManager.location?.speed ?? 0
|
||||
if currentSpeed > 0 && (!currentSpeed.isNaN || !currentSpeed.isInfinite) {
|
||||
positionPacket.groundSpeed = UInt32(currentSpeed * 3.6)
|
||||
|
|
@ -1053,17 +1043,15 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
positionPacket.groundTrack = UInt32(currentHeading)
|
||||
}
|
||||
}
|
||||
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
|
||||
return positionPacket
|
||||
}
|
||||
|
||||
public func setFixedPosition(fromUser: UserEntity, channel: Int32) -> Bool {
|
||||
var adminPacket = AdminMessage()
|
||||
guard let positionPacket = getPositionFromPhoneGPS(channel: channel, destNum: fromUser.num) else {
|
||||
guard let positionPacket = getPositionFromPhoneGPS(destNum: fromUser.num) else {
|
||||
return false
|
||||
}
|
||||
adminPacket.setFixedPosition = positionPacket
|
||||
|
|
@ -1109,7 +1097,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
public func sendPosition(channel: Int32, destNum: Int64, wantResponse: Bool) -> Bool {
|
||||
var success = false
|
||||
let fromNodeNum = connectedPeripheral.num
|
||||
guard let positionPacket = getPositionFromPhoneGPS(channel: channel, destNum: destNum) else {
|
||||
guard let positionPacket = getPositionFromPhoneGPS(destNum: destNum) else {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import Foundation
|
||||
import CoreData
|
||||
import SwiftUI
|
||||
import RegexBuilder
|
||||
#if canImport(ActivityKit)
|
||||
import ActivityKit
|
||||
#endif
|
||||
|
|
@ -160,12 +161,14 @@ func channelPacket (channel: Channel, fromNum: Int64, context: NSManagedObjectCo
|
|||
newChannel.name = channel.settings.name
|
||||
newChannel.role = Int32(channel.role.rawValue)
|
||||
newChannel.psk = channel.settings.psk
|
||||
newChannel.positionPrecision = Int32(truncatingIfNeeded: channel.settings.moduleSettings.positionPrecision)
|
||||
if channel.settings.hasModuleSettings {
|
||||
newChannel.positionPrecision = Int32(truncatingIfNeeded: channel.settings.moduleSettings.positionPrecision)
|
||||
newChannel.mute = channel.settings.moduleSettings.isClientMuted
|
||||
}
|
||||
guard let mutableChannels = fetchedMyInfo[0].channels!.mutableCopy() as? NSMutableOrderedSet else {
|
||||
return
|
||||
}
|
||||
if let oldChannel = mutableChannels.first(where: {($0 as AnyObject).index == newChannel.index }) as? ChannelEntity {
|
||||
newChannel.mute = oldChannel.mute
|
||||
let index = mutableChannels.index(of: oldChannel as Any)
|
||||
mutableChannels.replaceObject(at: index, with: newChannel)
|
||||
} else {
|
||||
|
|
@ -772,7 +775,19 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage
|
|||
func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connectedNode: Int64, storeForward: Bool = false, context: NSManagedObjectContext) {
|
||||
|
||||
var messageText = String(bytes: packet.decoded.payload, encoding: .utf8)
|
||||
if !wantRangeTestPackets && (String(messageText ?? "seq ").starts(with: "seq ")) {
|
||||
let rangeRef = Reference(Int.self)
|
||||
let rangeTestRegex = Regex {
|
||||
"seq "
|
||||
|
||||
TryCapture(as: rangeRef) {
|
||||
OneOrMore(.digit)
|
||||
} transform: { match in
|
||||
Int(match)
|
||||
}
|
||||
}
|
||||
let rangeTest = messageText?.contains(rangeTestRegex) ?? false && messageText?.starts(with: "seq ") ?? false
|
||||
|
||||
if !wantRangeTestPackets && rangeTest {
|
||||
return
|
||||
}
|
||||
var storeForwardBroadcast = false
|
||||
|
|
@ -841,26 +856,28 @@ func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connec
|
|||
return
|
||||
}
|
||||
let appState = AppState.shared
|
||||
if newMessage.fromUser != nil && newMessage.toUser != nil && !(newMessage.fromUser?.mute ?? false) {
|
||||
if newMessage.fromUser != nil && newMessage.toUser != nil {
|
||||
// Set Unread Message Indicators
|
||||
if packet.to == connectedNode {
|
||||
appState.unreadDirectMessages = newMessage.toUser?.unreadMessages ?? 0
|
||||
UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages
|
||||
}
|
||||
// Create an iOS Notification for the received DM message and schedule it immediately
|
||||
let manager = LocalNotificationManager()
|
||||
manager.notifications = [
|
||||
Notification(
|
||||
id: ("notification.id.\(newMessage.messageId)"),
|
||||
title: "\(newMessage.fromUser?.longName ?? "unknown".localized)",
|
||||
subtitle: "AKA \(newMessage.fromUser?.shortName ?? "?")",
|
||||
content: messageText!,
|
||||
target: "message",
|
||||
path: "meshtastic://open-dm?userid=\(newMessage.fromUser?.num ?? 0)&id=\(newMessage.messageId)"
|
||||
)
|
||||
]
|
||||
manager.schedule()
|
||||
print("💬 iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "unknown".localized)")
|
||||
if !(newMessage.fromUser?.mute ?? false) {
|
||||
// Create an iOS Notification for the received DM message and schedule it immediately
|
||||
let manager = LocalNotificationManager()
|
||||
manager.notifications = [
|
||||
Notification(
|
||||
id: ("notification.id.\(newMessage.messageId)"),
|
||||
title: "\(newMessage.fromUser?.longName ?? "unknown".localized)",
|
||||
subtitle: "AKA \(newMessage.fromUser?.shortName ?? "?")",
|
||||
content: messageText!,
|
||||
target: "message",
|
||||
path: "meshtastic://open-dm?userid=\(newMessage.fromUser?.num ?? 0)&id=\(newMessage.messageId)"
|
||||
)
|
||||
]
|
||||
manager.schedule()
|
||||
print("💬 iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "unknown".localized)")
|
||||
}
|
||||
} else if newMessage.fromUser != nil && newMessage.toUser == nil {
|
||||
|
||||
let fetchMyInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MyInfoEntity")
|
||||
|
|
@ -878,7 +895,7 @@ func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connec
|
|||
if channel.index == newMessage.channel {
|
||||
context.refresh(channel, mergeChanges: true)
|
||||
}
|
||||
if channel.index == newMessage.channel && !channel.mute {
|
||||
if channel.index == newMessage.channel && !channel.mute && UserDefaults.channelMessageNotifications {
|
||||
// Create an iOS Notification for the received private channel message and schedule it immediately
|
||||
let manager = LocalNotificationManager()
|
||||
manager.notifications = [
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>MeshtasticDataModelV 35.xcdatamodel</string>
|
||||
<string>MeshtasticDataModelV 36.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,461 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22757" systemVersion="23F5064f" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="AmbientLightingConfigEntity" representedClassName="AmbientLightingConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="blue" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="current" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="green" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ledState" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="red" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="ambientLightingConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="ambientLightingConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="BluetoothConfigEntity" representedClassName="BluetoothConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="fixedPin" optional="YES" attributeType="Integer 32" defaultValueString="123456" usesScalarValueType="YES"/>
|
||||
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="bluetoothConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="bluetoothConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="CannedMessageConfigEntity" representedClassName="CannedMessageConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventCcw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventCw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventPress" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinA" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinB" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinPress" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="messages" optional="YES" attributeType="String" minValueString="0" maxValueString="198"/>
|
||||
<attribute name="rotary1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="updown1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="cannedMessagesConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="cannedMessageConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="ChannelEntity" representedClassName="ChannelEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="downlinkEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="index" attributeType="Integer 32" minValueString="0" maxValueString="13" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="positionPrecision" optional="YES" attributeType="Integer 32" defaultValueString="32" usesScalarValueType="YES"/>
|
||||
<attribute name="psk" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="uplinkEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="myInfoChannel" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="channels" inverseEntity="MyInfoEntity"/>
|
||||
<fetchedProperty name="allPrivateMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="channel == $FETCH_SOURCE.index && toUser == nil AND isEmoji == false"/>
|
||||
</fetchedProperty>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="index"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="DetectionSensorConfigEntity" representedClassName="DetectionSensorConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="detectionTriggeredHigh" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="minimumBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="monitorPin" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="stateBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="usePullup" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="detectionSensorConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="detectionSensorConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DeviceConfigEntity" representedClassName="DeviceConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="buttonGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="buzzerGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="debugLogEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="disableTripleClick" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="doubleTapAsButtonPress" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="isManaged" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="ledHeartbeatEnabled" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="nodeInfoBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rebroadcastMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="serialEnabled" optional="YES" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="tzdef" optional="YES" attributeType="String"/>
|
||||
<relationship name="deviceConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="deviceConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DeviceMetadataEntity" representedClassName="DeviceMetadataEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="canShutdown" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceStateVersion" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="firmwareVersion" optional="YES" attributeType="String"/>
|
||||
<attribute name="hasBluetooth" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasEthernet" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasWifi" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hwModel" optional="YES" attributeType="String"/>
|
||||
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="metadataNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="metadata" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DisplayConfigEntity" representedClassName="DisplayConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="compassNorthTop" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="displayMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="flipScreen" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsFormat" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="headingBold" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="oledType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="screenCarouselInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="screenOnSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="units" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wakeOnTapOrMotion" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="displayConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="displayConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="ExternalNotificationConfigEntity" representedClassName="ExternalNotificationConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="active" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBellBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBellVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessage" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessageBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessageVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="nagTimeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="output" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputBuzzer" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputMilliseconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputVibra" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="useI2SAsBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="usePWM" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<relationship name="externalNotificationConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="externalNotificationConfig" inverseEntity="NodeInfoEntity"/>
|
||||
<fetchedProperty name="fetchedProperty" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="ExternalNotificationConfigEntity"/>
|
||||
</fetchedProperty>
|
||||
</entity>
|
||||
<entity name="LocationEntity" representedClassName="LocationEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="routeLocation" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RouteEntity" inverseName="locations" inverseEntity="RouteEntity"/>
|
||||
</entity>
|
||||
<entity name="LoRaConfigEntity" representedClassName="LoRaConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bandwidth" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="channelNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="codingRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="frequencyOffset" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="hopLimit" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ignoreMqtt" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="modemPreset" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideDutyCycle" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideFrequency" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="regionCode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="spreadFactor" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="sx126xRxBoostedGain" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="txEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="txPower" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="usePreset" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<relationship name="loRaConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="loRaConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="MessageEntity" representedClassName="MessageEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ackError" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ackSNR" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="ackTimestamp" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="admin" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="adminDescription" optional="YES" attributeType="String"/>
|
||||
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isEmoji" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="messageId" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="messagePayload" optional="YES" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="messagePayloadMarkdown" optional="YES" attributeType="String"/>
|
||||
<attribute name="messageTimestamp" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="portNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="read" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="realACK" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="receivedACK" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="receivedTimestamp" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="replyID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<relationship name="fromUser" optional="YES" maxCount="1" deletionRule="Nullify" ordered="YES" destinationEntity="UserEntity" inverseName="sentMessages" inverseEntity="UserEntity"/>
|
||||
<relationship name="toUser" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="receivedMessages" inverseEntity="UserEntity"/>
|
||||
<fetchedProperty name="tapbacks" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="replyID == $FETCH_SOURCE.messageId AND isEmoji == true"/>
|
||||
</fetchedProperty>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="messageId"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="MQTTConfigEntity" representedClassName="MQTTConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="address" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="encryptionEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="jsonEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="mapPositionPrecision" optional="YES" attributeType="Integer 32" defaultValueString="13" usesScalarValueType="YES"/>
|
||||
<attribute name="mapPublishIntervalSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="mapReportingEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="password" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<attribute name="proxyToClientEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="root" optional="YES" attributeType="String" defaultValueString="msh"/>
|
||||
<attribute name="tlsEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="username" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<relationship name="mqttConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="mqttConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="MyInfoEntity" representedClassName="MyInfoEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="adminIndex" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="bleName" optional="YES" attributeType="String"/>
|
||||
<attribute name="minAppVersion" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="myNodeNum" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="peripheralId" optional="YES" attributeType="String"/>
|
||||
<attribute name="rebootCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="channels" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="ChannelEntity" inverseName="myInfoChannel" inverseEntity="ChannelEntity"/>
|
||||
<relationship name="myInfoNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="myInfo" inverseEntity="NodeInfoEntity"/>
|
||||
<fetchedProperty name="allMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="toUser == nil"/>
|
||||
</fetchedProperty>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="myNodeNum"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="NetworkConfigEntity" representedClassName="NetworkConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="dns" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ethEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="gateway" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ip" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ntpServer" optional="YES" attributeType="String"/>
|
||||
<attribute name="subnet" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiMode" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiPsk" optional="YES" attributeType="String" minValueString="0" maxValueString="60"/>
|
||||
<attribute name="wifiSsid" optional="YES" attributeType="String" minValueString="0" maxValueString="30"/>
|
||||
<relationship name="networkConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="networkConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="NodeInfoEntity" representedClassName="NodeInfoEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bleName" optional="YES" attributeType="String"/>
|
||||
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="favorite" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="hopsAway" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="lastHeard" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="peripheralId" optional="YES" attributeType="String"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="viaMqtt" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="ambientLightingConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="AmbientLightingConfigEntity" inverseName="ambientLightingConfigNode" inverseEntity="AmbientLightingConfigEntity"/>
|
||||
<relationship name="bluetoothConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="BluetoothConfigEntity" inverseName="bluetoothConfigNode" inverseEntity="BluetoothConfigEntity"/>
|
||||
<relationship name="cannedMessageConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="CannedMessageConfigEntity" inverseName="cannedMessagesConfigNode" inverseEntity="CannedMessageConfigEntity"/>
|
||||
<relationship name="detectionSensorConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DetectionSensorConfigEntity" inverseName="detectionSensorConfigNode" inverseEntity="DetectionSensorConfigEntity"/>
|
||||
<relationship name="deviceConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DeviceConfigEntity" inverseName="deviceConfigNode" inverseEntity="DeviceConfigEntity"/>
|
||||
<relationship name="displayConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DisplayConfigEntity" inverseName="displayConfigNode" inverseEntity="DisplayConfigEntity"/>
|
||||
<relationship name="externalNotificationConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ExternalNotificationConfigEntity" inverseName="externalNotificationConfigNode" inverseEntity="ExternalNotificationConfigEntity"/>
|
||||
<relationship name="loRaConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="LoRaConfigEntity" inverseName="loRaConfigNode" inverseEntity="LoRaConfigEntity"/>
|
||||
<relationship name="metadata" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DeviceMetadataEntity" inverseName="metadataNode" inverseEntity="DeviceMetadataEntity"/>
|
||||
<relationship name="mqttConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MQTTConfigEntity" inverseName="mqttConfigNode" inverseEntity="MQTTConfigEntity"/>
|
||||
<relationship name="myInfo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="myInfoNode" inverseEntity="MyInfoEntity"/>
|
||||
<relationship name="networkConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NetworkConfigEntity" inverseName="networkConfigNode" inverseEntity="NetworkConfigEntity"/>
|
||||
<relationship name="pax" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PaxCounterEntity" inverseName="paxNode" inverseEntity="PaxCounterEntity"/>
|
||||
<relationship name="paxCounterConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PaxCounterConfigEntity" inverseName="paxCounterConfigNode" inverseEntity="PaxCounterConfigEntity"/>
|
||||
<relationship name="positionConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PositionConfigEntity" inverseName="positionConfigNode" inverseEntity="PositionConfigEntity"/>
|
||||
<relationship name="positions" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PositionEntity" inverseName="nodePosition" inverseEntity="PositionEntity"/>
|
||||
<relationship name="powerConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PowerConfigEntity" inverseName="powerConfigNode" inverseEntity="PowerConfigEntity"/>
|
||||
<relationship name="rangeTestConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RangeTestConfigEntity" inverseName="rangeTestConfigNode" inverseEntity="RangeTestConfigEntity"/>
|
||||
<relationship name="rtttlConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RTTTLConfigEntity" inverseName="rtttlConfigNode" inverseEntity="RTTTLConfigEntity"/>
|
||||
<relationship name="serialConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SerialConfigEntity" inverseName="serialConfigNode" inverseEntity="SerialConfigEntity"/>
|
||||
<relationship name="storeForwardConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreForwardConfigEntity" inverseName="storeForwardConfigNode" inverseEntity="StoreForwardConfigEntity"/>
|
||||
<relationship name="telemetries" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TelemetryEntity" inverseName="nodeTelemetry" inverseEntity="TelemetryEntity"/>
|
||||
<relationship name="telemetryConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TelemetryConfigEntity" inverseName="telemetryConfigNode" inverseEntity="TelemetryConfigEntity"/>
|
||||
<relationship name="traceRoutes" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TraceRouteEntity" inverseName="node" inverseEntity="TraceRouteEntity"/>
|
||||
<relationship name="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="userNode" inverseEntity="UserEntity"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="num"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="PaxCounterConfigEntity" representedClassName="PaxCounterConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="paxcounterUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="paxCounterConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="paxCounterConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PaxCounterEntity" representedClassName="PaxCounterEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ble" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="uptime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wifi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="paxNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="pax" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PositionConfigEntity" representedClassName="PositionConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="broadcastSmartMinimumDistance" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="broadcastSmartMinimumIntervalSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceGpsEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="fixedPosition" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsAttemptTime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsEnGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="positionBroadcastSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rxGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="smartPositionEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="txGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="positionConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positionConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PositionEntity" representedClassName="PositionEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="latest" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="precisionBits" optional="YES" attributeType="Integer 32" defaultValueString="32" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="satsInView" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="seqNo" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="nodePosition" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positions" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PowerConfigEntity" representedClassName="PowerConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="adcMultiplierOverride" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceBatteryInaAddress" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isPowerSaving" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="lsSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="minWakeSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="onBatteryShutdownAfterSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="waitBluetoothSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="powerConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="powerConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="RangeTestConfigEntity" representedClassName="RangeTestConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="save" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="sender" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<relationship name="rangeTestConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rangeTestConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="RouteEntity" representedClassName="RouteEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="color" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="distance" optional="YES" attributeType="Double" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="elevationGain" optional="YES" attributeType="Double" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="endDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="notes" optional="YES" attributeType="String"/>
|
||||
<relationship name="locations" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="LocationEntity" inverseName="routeLocation" inverseEntity="LocationEntity"/>
|
||||
</entity>
|
||||
<entity name="RTTTLConfigEntity" representedClassName="RTTTLConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ringtone" optional="YES" attributeType="String" maxValueString="228" defaultValueString=""/>
|
||||
<relationship name="rtttlConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rtttlConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="SerialConfigEntity" representedClassName="SerialConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="baudRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="echo" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideConsoleSerialPort" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="rxd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="timeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="txd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="serialConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="serialConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="StoreForwardConfigEntity" representedClassName="StoreForwardConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="heartbeat" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="historyReturnMax" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="historyReturnWindow" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isRouter" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="lastHeartbeat" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="lastRequest" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="records" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="storeForwardConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="storeForwardConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TelemetryConfigEntity" representedClassName="TelemetryConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="deviceUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentDisplayFahrenheit" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentMeasurementEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentScreenEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="powerMeasurementEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="powerScreenEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="powerUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="telemetryConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetryConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TelemetryEntity" representedClassName="TelemetryEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="airUtilTx" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="barometricPressure" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="batteryLevel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="channelUtilization" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="current" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="gasResistance" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="iaq" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="metricsType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="relativeHumidity" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="temperature" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="uptimeSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="voltage" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<relationship name="nodeTelemetry" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetries" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TraceRouteEntity" representedClassName="TraceRouteEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="hasPositions" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="num" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="response" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="route" optional="YES" attributeType="Transformable" customClassName="[UInt32]"/>
|
||||
<attribute name="routeText" optional="YES" attributeType="String"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="hops" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TraceRouteHopEntity" inverseName="traceRoute" inverseEntity="TraceRouteHopEntity"/>
|
||||
<relationship name="node" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="traceRoutes" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TraceRouteHopEntity" representedClassName="TraceRouteHopEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="num" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="traceRoute" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TraceRouteEntity" inverseName="hops" inverseEntity="TraceRouteEntity"/>
|
||||
</entity>
|
||||
<entity name="UserEntity" representedClassName="UserEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="hwModel" attributeType="String"/>
|
||||
<attribute name="isLicensed" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="lastMessage" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="longName" attributeType="String"/>
|
||||
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numString" optional="YES" attributeType="String"/>
|
||||
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="shortName" attributeType="String"/>
|
||||
<attribute name="userId" attributeType="String"/>
|
||||
<relationship name="receivedMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="toUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="sentMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="fromUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="userNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="user" inverseEntity="NodeInfoEntity"/>
|
||||
<fetchedProperty name="adminMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="(fromUser.num == $FETCH_SOURCE.num) AND isEmoji == false AND admin = true"/>
|
||||
</fetchedProperty>
|
||||
<fetchedProperty name="allMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="((toUser.num == $FETCH_SOURCE.num) OR (fromUser.num == $FETCH_SOURCE.num)) AND toUser != nil AND fromUser != nil AND isEmoji == false AND admin = false AND portNum != 10 "/>
|
||||
</fetchedProperty>
|
||||
<fetchedProperty name="detectionSensorMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="(fromUser.num == $FETCH_SOURCE.num) AND portNum = 10"/>
|
||||
</fetchedProperty>
|
||||
</entity>
|
||||
<entity name="WaypointEntity" representedClassName="WaypointEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="created" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="expire" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="icon" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="lastUpdated" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="locked" attributeType="Integer 64" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="longDescription" optional="YES" attributeType="String" maxValueString="100"/>
|
||||
<attribute name="longitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" attributeType="String" minValueString="1" maxValueString="30"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="id"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
</model>
|
||||
|
|
@ -462,6 +462,7 @@ func upsertDeviceConfigPacket(config: Meshtastic.Config.DeviceConfig, nodeNum: I
|
|||
newDeviceConfig.rebroadcastMode = Int32(config.rebroadcastMode.rawValue)
|
||||
newDeviceConfig.nodeInfoBroadcastSecs = Int32(truncating: config.nodeInfoBroadcastSecs as NSNumber)
|
||||
newDeviceConfig.doubleTapAsButtonPress = config.doubleTapAsButtonPress
|
||||
newDeviceConfig.ledHeartbeatEnabled = !config.ledHeartbeatDisabled
|
||||
newDeviceConfig.isManaged = config.isManaged
|
||||
newDeviceConfig.tzdef = config.tzdef
|
||||
fetchedNode[0].deviceConfig = newDeviceConfig
|
||||
|
|
@ -474,6 +475,7 @@ func upsertDeviceConfigPacket(config: Meshtastic.Config.DeviceConfig, nodeNum: I
|
|||
fetchedNode[0].deviceConfig?.rebroadcastMode = Int32(config.rebroadcastMode.rawValue)
|
||||
fetchedNode[0].deviceConfig?.nodeInfoBroadcastSecs = Int32(truncating: config.nodeInfoBroadcastSecs as NSNumber)
|
||||
fetchedNode[0].deviceConfig?.doubleTapAsButtonPress = config.doubleTapAsButtonPress
|
||||
fetchedNode[0].deviceConfig?.ledHeartbeatEnabled = !config.ledHeartbeatDisabled
|
||||
fetchedNode[0].deviceConfig?.isManaged = config.isManaged
|
||||
fetchedNode[0].deviceConfig?.tzdef = config.tzdef
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,6 +120,11 @@ struct ModuleSettings {
|
|||
/// Bits of precision for the location sent in position packets.
|
||||
var positionPrecision: UInt32 = 0
|
||||
|
||||
///
|
||||
/// Controls whether or not the phone / clients should mute the current channel
|
||||
/// Useful for noisy public channels you don't necessarily want to disable
|
||||
var isClientMuted: Bool = false
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
|
|
@ -311,6 +316,7 @@ extension ModuleSettings: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement
|
|||
static let protoMessageName: String = _protobuf_package + ".ModuleSettings"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .standard(proto: "position_precision"),
|
||||
2: .standard(proto: "is_client_muted"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
|
|
@ -320,6 +326,7 @@ extension ModuleSettings: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement
|
|||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try { try decoder.decodeSingularUInt32Field(value: &self.positionPrecision) }()
|
||||
case 2: try { try decoder.decodeSingularBoolField(value: &self.isClientMuted) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
|
@ -329,11 +336,15 @@ extension ModuleSettings: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement
|
|||
if self.positionPrecision != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.positionPrecision, fieldNumber: 1)
|
||||
}
|
||||
if self.isClientMuted != false {
|
||||
try visitor.visitSingularBoolField(value: self.isClientMuted, fieldNumber: 2)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: ModuleSettings, rhs: ModuleSettings) -> Bool {
|
||||
if lhs.positionPrecision != rhs.positionPrecision {return false}
|
||||
if lhs.isClientMuted != rhs.isClientMuted {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -266,6 +266,18 @@ struct Connect: View {
|
|||
.controlSize(.large)
|
||||
.padding()
|
||||
}
|
||||
if bleManager.isConnecting {
|
||||
Button(role: .destructive, action: {
|
||||
bleManager.cancelPeripheralConnection()
|
||||
|
||||
}) {
|
||||
Label("disconnect", systemImage: "antenna.radiowaves.left.and.right.slash")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
}
|
||||
#endif
|
||||
Spacer()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,10 +29,8 @@ struct CircleText: View {
|
|||
|
||||
struct CircleText_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
|
||||
HStack {
|
||||
VStack {
|
||||
|
||||
VStack {
|
||||
HStack {
|
||||
CircleText(text: "N1", color: Color.yellow, circleSize: 80)
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
CircleText(text: "8", color: Color.purple, circleSize: 80)
|
||||
|
|
@ -41,17 +39,20 @@ struct CircleText_Previews: PreviewProvider {
|
|||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
CircleText(text: "🍔", color: Color.brown, circleSize: 80)
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
}
|
||||
HStack {
|
||||
CircleText(text: "👻", color: Color.orange, circleSize: 80)
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
CircleText(text: "🤙", color: Color.orange, circleSize: 80)
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
|
||||
}
|
||||
VStack {
|
||||
CircleText(text: "69", color: Color.green, circleSize: 80)
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
CircleText(text: "WWWW", color: Color.cyan, circleSize: 80)
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
}
|
||||
HStack {
|
||||
|
||||
|
||||
CircleText(text: "CW-A", color: Color.secondary)
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
CircleText(text: "CW-A", color: Color.secondary, circleSize: 80)
|
||||
|
|
@ -60,7 +61,20 @@ struct CircleText_Previews: PreviewProvider {
|
|||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
CircleText(text: "IIII", color: Color.accentColor, circleSize: 80)
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
CircleText(text: "LCP", color: Color.primary, circleSize: 80)
|
||||
}
|
||||
HStack {
|
||||
|
||||
CircleText(text: "🚗", color: Color.orange)
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
CircleText(text: "🔋", color: Color.indigo, circleSize: 80)
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
CircleText(text: "🛢️", color: Color.orange, circleSize: 80)
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
CircleText(text: "LCP", color: Color.indigo, circleSize: 80)
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
}
|
||||
HStack {
|
||||
CircleText(text: "🤡", color: Color.red, circleSize: 80)
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,125 +0,0 @@
|
|||
//
|
||||
// IndoorAirQuality.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Copyright(c) by Garth Vander Houwen on 4/10/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
enum IaqDisplayMode: Int, CaseIterable, Identifiable {
|
||||
|
||||
case pill = 0
|
||||
case dot = 1
|
||||
case text = 2
|
||||
case gauge = 3
|
||||
|
||||
var id: Int { self.rawValue }
|
||||
}
|
||||
|
||||
struct IndoorAirQuality: View {
|
||||
var iaq: Int = 0
|
||||
var displayMode: IaqDisplayMode = .pill
|
||||
let gradient = Gradient(colors: [.green, .mint, .yellow, .orange, .red, .purple, .purple, .brown, .brown, .brown, .brown])
|
||||
|
||||
var body: some View {
|
||||
let iaqEnum = Iaq.getIaq(for: iaq)
|
||||
switch displayMode {
|
||||
case .pill:
|
||||
ZStack (alignment: .leading) {
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.fill(iaqEnum.color)
|
||||
.frame(width: 125, height: 30)
|
||||
Label("IAQ \(iaq)", systemImage: iaq < 100 ? "aqi.low" : ((iaq > 100 && iaq < 201) ? "aqi.medium" : "aqi.high"))
|
||||
.padding(.leading, 4)
|
||||
}
|
||||
case .dot:
|
||||
VStack {
|
||||
HStack {
|
||||
Text("\(iaq)")
|
||||
Circle()
|
||||
.fill(iaqEnum.color)
|
||||
.frame(width: 10, height: 10)
|
||||
}
|
||||
}
|
||||
case .text:
|
||||
Text(iaqEnum.description)
|
||||
.font(.caption)
|
||||
case .gauge:
|
||||
Gauge(value: Double(iaq), in: 0...500) {
|
||||
|
||||
Text("IAQ")
|
||||
.foregroundColor(iaqEnum.color)
|
||||
} currentValueLabel: {
|
||||
Text("\(Int(iaq))")
|
||||
}
|
||||
.tint(gradient)
|
||||
.gaugeStyle(.accessoryCircular)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct IndoorAirQuality_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
Text(".pill")
|
||||
.font(.title)
|
||||
HStack {
|
||||
VStack{
|
||||
IndoorAirQuality(iaq: 6)
|
||||
IndoorAirQuality(iaq: 51)
|
||||
IndoorAirQuality(iaq: 101)
|
||||
}
|
||||
VStack {
|
||||
IndoorAirQuality(iaq: 201)
|
||||
IndoorAirQuality(iaq: 350)
|
||||
IndoorAirQuality(iaq: 351)
|
||||
}
|
||||
}
|
||||
Text(".dot")
|
||||
.font(.title)
|
||||
HStack {
|
||||
VStack (alignment: .leading) {
|
||||
IndoorAirQuality(iaq: 6, displayMode: .dot)
|
||||
IndoorAirQuality(iaq: 51, displayMode: .dot)
|
||||
IndoorAirQuality(iaq: 101, displayMode: .dot)
|
||||
}
|
||||
VStack (alignment: .leading) {
|
||||
IndoorAirQuality(iaq: 201, displayMode: .dot)
|
||||
IndoorAirQuality(iaq: 350, displayMode: .dot)
|
||||
IndoorAirQuality(iaq: 351, displayMode: .dot)
|
||||
}
|
||||
}
|
||||
Text(".text")
|
||||
.font(.title)
|
||||
IndoorAirQuality(iaq: 6, displayMode: .text)
|
||||
IndoorAirQuality(iaq: 51, displayMode: .text)
|
||||
IndoorAirQuality(iaq: 101, displayMode: .text)
|
||||
IndoorAirQuality(iaq: 201, displayMode: .text)
|
||||
IndoorAirQuality(iaq: 350, displayMode: .text)
|
||||
IndoorAirQuality(iaq: 351, displayMode: .text)
|
||||
Text(".gauge")
|
||||
.font(.title)
|
||||
HStack (alignment: .top) {
|
||||
VStack{
|
||||
IndoorAirQuality(iaq: 6, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 51, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 101, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 151, displayMode: .gauge)
|
||||
}
|
||||
VStack{
|
||||
IndoorAirQuality(iaq: 201, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 251, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 301, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 350, displayMode: .gauge)
|
||||
}
|
||||
VStack{
|
||||
IndoorAirQuality(iaq: 351, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 401, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 500, displayMode: .gauge)
|
||||
}
|
||||
}
|
||||
}.previewLayout(.fixed(width: 300, height: 800))
|
||||
}
|
||||
}
|
||||
146
Meshtastic/Views/Helpers/Weather/AirQualityIndex.swift
Normal file
146
Meshtastic/Views/Helpers/Weather/AirQualityIndex.swift
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
//
|
||||
// AQICircleDisplay.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Copyright(c) Garth Vander Houwen 2/4/23.
|
||||
//
|
||||
import SwiftUI
|
||||
|
||||
enum AqiDisplayMode: Int, CaseIterable, Identifiable {
|
||||
|
||||
case pill = 0
|
||||
case dot = 1
|
||||
case text = 2
|
||||
case gauge = 3
|
||||
case gradient = 4
|
||||
|
||||
var id: Int { self.rawValue }
|
||||
}
|
||||
|
||||
struct AirQualityIndex: View {
|
||||
var aqi: Int
|
||||
var displayMode: IaqDisplayMode = .pill
|
||||
let gradient = Gradient(colors: [.green, .yellow, .orange, .red, .purple, .magenta])
|
||||
|
||||
var body: some View {
|
||||
|
||||
let aqiEnum = Aqi.getAqi(for: aqi)
|
||||
switch displayMode {
|
||||
case .pill:
|
||||
ZStack (alignment: .leading) {
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.fill(aqiEnum.color)
|
||||
.frame(width: 125, height: 30)
|
||||
Label("IAQ \(aqi)", systemImage: aqi < 100 ? "aqi.low" : ((aqi > 100 && aqi < 201) ? "aqi.medium" : "aqi.high"))
|
||||
.padding(.leading, 4)
|
||||
}
|
||||
case .dot:
|
||||
VStack {
|
||||
HStack {
|
||||
Text("\(aqi)")
|
||||
Circle()
|
||||
.fill(aqiEnum.color)
|
||||
.frame(width: 10, height: 10)
|
||||
}
|
||||
}
|
||||
case .text:
|
||||
Text(aqiEnum.description)
|
||||
.font(.caption)
|
||||
case .gauge:
|
||||
Gauge(value: Double(aqi), in: 0...500) {
|
||||
|
||||
Text("IAQ")
|
||||
.foregroundColor(aqiEnum.color)
|
||||
} currentValueLabel: {
|
||||
Text("\(Int(aqi))")
|
||||
}
|
||||
.tint(gradient)
|
||||
.gaugeStyle(.accessoryCircular)
|
||||
case .gradient:
|
||||
HStack {
|
||||
Gauge(value: Double(aqi), in: 0...500) {
|
||||
Text("IAQ")
|
||||
.foregroundColor(aqiEnum.color)
|
||||
} currentValueLabel: {
|
||||
Text("IAQ ")+Text("\(Int(aqi))")
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.tint(gradient)
|
||||
.gaugeStyle(.accessoryLinear)
|
||||
Text(aqiEnum.description)
|
||||
.font(.caption)
|
||||
}
|
||||
.padding([.leading, .trailing])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AirQualityIndex_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
Text(".pill")
|
||||
.font(.title2)
|
||||
HStack {
|
||||
AirQualityIndex(aqi: 6)
|
||||
AirQualityIndex(aqi: 51)
|
||||
}
|
||||
HStack {
|
||||
AirQualityIndex(aqi: 101)
|
||||
AirQualityIndex(aqi: 151)
|
||||
}
|
||||
HStack {
|
||||
AirQualityIndex(aqi: 201)
|
||||
AirQualityIndex(aqi: 351)
|
||||
}
|
||||
Text(".dot")
|
||||
.font(.title2)
|
||||
HStack {
|
||||
AirQualityIndex(aqi: 6, displayMode: .dot)
|
||||
AirQualityIndex(aqi: 51, displayMode: .dot)
|
||||
AirQualityIndex(aqi: 101, displayMode: .dot)
|
||||
AirQualityIndex(aqi: 201, displayMode: .dot)
|
||||
AirQualityIndex(aqi: 350, displayMode: .dot)
|
||||
AirQualityIndex(aqi: 351, displayMode: .dot)
|
||||
}
|
||||
Text(".text")
|
||||
.font(.title2)
|
||||
HStack {
|
||||
AirQualityIndex(aqi: 6, displayMode: .text)
|
||||
AirQualityIndex(aqi: 51, displayMode: .text)
|
||||
AirQualityIndex(aqi: 101, displayMode: .text)
|
||||
}
|
||||
HStack {
|
||||
AirQualityIndex(aqi: 201, displayMode: .text)
|
||||
AirQualityIndex(aqi: 350, displayMode: .text)
|
||||
}
|
||||
Text(".gauge")
|
||||
.font(.title2)
|
||||
HStack (alignment: .top) {
|
||||
AirQualityIndex(aqi: 6, displayMode: .gauge)
|
||||
AirQualityIndex(aqi: 51, displayMode: .gauge)
|
||||
AirQualityIndex(aqi: 101, displayMode: .gauge)
|
||||
AirQualityIndex(aqi: 151, displayMode: .gauge)
|
||||
}
|
||||
HStack (alignment: .top) {
|
||||
AirQualityIndex(aqi: 201, displayMode: .gauge)
|
||||
AirQualityIndex(aqi: 251, displayMode: .gauge)
|
||||
AirQualityIndex(aqi: 301, displayMode: .gauge)
|
||||
AirQualityIndex(aqi: 351, displayMode: .gauge)
|
||||
}
|
||||
HStack (alignment: .top) {
|
||||
AirQualityIndex(aqi: 401, displayMode: .gauge)
|
||||
AirQualityIndex(aqi: 500, displayMode: .gauge)
|
||||
}
|
||||
Text(".gradient")
|
||||
.font(.title2)
|
||||
AirQualityIndex(aqi: 6, displayMode: .gradient)
|
||||
AirQualityIndex(aqi: 51, displayMode: .gradient)
|
||||
AirQualityIndex(aqi: 101, displayMode: .gradient)
|
||||
AirQualityIndex(aqi: 201, displayMode: .gradient)
|
||||
AirQualityIndex(aqi: 351, displayMode: .gradient)
|
||||
AirQualityIndex(aqi: 401, displayMode: .gradient)
|
||||
AirQualityIndex(aqi: 500, displayMode: .gradient)
|
||||
|
||||
}.previewLayout(.fixed(width: 300, height: 800))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
//
|
||||
// AQICircleDisplay.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Copyright(c) Garth Vander Houwen 2/4/23.
|
||||
//
|
||||
import SwiftUI
|
||||
|
||||
struct AirQualityIndexCompact: View {
|
||||
var aqi: Int
|
||||
|
||||
var body: some View {
|
||||
|
||||
HStack(spacing: 0.5) {
|
||||
Text("AQI \(aqi)")
|
||||
.foregroundColor(.gray)
|
||||
.padding(.trailing, 0)
|
||||
.font(.caption)
|
||||
|
||||
if aqi > 0 && aqi < 51 {
|
||||
// Good
|
||||
Circle()
|
||||
.fill(.green)
|
||||
.frame(width: 10, height: 10)
|
||||
} else if aqi > 50 && aqi < 101 {
|
||||
// Satisfactory
|
||||
Circle()
|
||||
.fill(Color(red: 0, green: 0.9882, blue: 0.1804))
|
||||
.frame(width: 10, height: 10)
|
||||
} else if aqi > 100 && aqi < 201 {
|
||||
// Moderate
|
||||
Circle()
|
||||
.fill(.yellow)
|
||||
.frame(width: 10, height: 10)
|
||||
} else if aqi > 200 && aqi < 301 {
|
||||
// Poor
|
||||
Circle()
|
||||
.fill(.orange)
|
||||
.frame(width: 10, height: 10)
|
||||
|
||||
} else if aqi > 300 && aqi < 401 {
|
||||
// Very Poor
|
||||
Circle()
|
||||
.fill(.red)
|
||||
.frame(width: 10, height: 10)
|
||||
} else if aqi >= 401 {
|
||||
// Very Poor
|
||||
Circle()
|
||||
.fill(Color(red: 0.8392, green: 0.0667, blue: 0))
|
||||
.frame(width: 10, height: 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
struct AQICircleDisplay_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
|
||||
VStack {
|
||||
AirQualityIndexCompact(aqi: 5)
|
||||
AirQualityIndexCompact(aqi: 51)
|
||||
AirQualityIndexCompact(aqi: 101)
|
||||
AirQualityIndexCompact(aqi: 201)
|
||||
AirQualityIndexCompact(aqi: 301)
|
||||
AirQualityIndexCompact(aqi: 401)
|
||||
}
|
||||
}
|
||||
}
|
||||
39
Meshtastic/Views/Helpers/Weather/IAQScale.swift
Normal file
39
Meshtastic/Views/Helpers/Weather/IAQScale.swift
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// IAQScale.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Garth Vander Houwen on 4/24/24.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct IAQScale: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment:.leading) {
|
||||
ForEach(Iaq.allCases) { iaq in
|
||||
HStack {
|
||||
RoundedRectangle(cornerRadius: 5)
|
||||
.fill(iaq.color)
|
||||
.frame(width: 30, height: 20)
|
||||
Text(iaq.description)
|
||||
.font(.callout)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(.thinMaterial, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
// .overlay(
|
||||
// RoundedRectangle(cornerRadius: 20)
|
||||
// .stroke(.secondary, lineWidth: 5)
|
||||
// )
|
||||
}
|
||||
}
|
||||
|
||||
struct IAQSCalePreviews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
IAQScale()
|
||||
}
|
||||
}
|
||||
}
|
||||
148
Meshtastic/Views/Helpers/Weather/IndoorAirQuality.swift
Normal file
148
Meshtastic/Views/Helpers/Weather/IndoorAirQuality.swift
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
//
|
||||
// IndoorAirQuality.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Copyright(c) by Garth Vander Houwen on 4/10/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
enum IaqDisplayMode: Int, CaseIterable, Identifiable {
|
||||
|
||||
case pill = 0
|
||||
case dot = 1
|
||||
case text = 2
|
||||
case gauge = 3
|
||||
case gradient = 4
|
||||
|
||||
var id: Int { self.rawValue }
|
||||
}
|
||||
|
||||
struct IndoorAirQuality: View {
|
||||
var iaq: Int = 0
|
||||
var displayMode: IaqDisplayMode = .pill
|
||||
let gradient = Gradient(colors: [.green, .mint, .yellow, .orange, .red, .purple, .purple, .brown, .brown, .brown, .brown])
|
||||
|
||||
var body: some View {
|
||||
let iaqEnum = Iaq.getIaq(for: iaq)
|
||||
switch displayMode {
|
||||
case .pill:
|
||||
ZStack (alignment: .leading) {
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.fill(iaqEnum.color)
|
||||
.frame(width: 125, height: 30)
|
||||
Label("IAQ \(iaq)", systemImage: iaq < 100 ? "aqi.low" : ((iaq > 100 && iaq < 201) ? "aqi.medium" : "aqi.high"))
|
||||
.padding(.leading, 4)
|
||||
}
|
||||
case .dot:
|
||||
VStack {
|
||||
HStack {
|
||||
Text("\(iaq)")
|
||||
Circle()
|
||||
.fill(iaqEnum.color)
|
||||
.frame(width: 10, height: 10)
|
||||
}
|
||||
}
|
||||
case .text:
|
||||
Text(iaqEnum.description)
|
||||
.font(.caption)
|
||||
case .gauge:
|
||||
Gauge(value: Double(iaq), in: 0...500) {
|
||||
|
||||
Text("IAQ")
|
||||
.foregroundColor(iaqEnum.color)
|
||||
} currentValueLabel: {
|
||||
Text("\(Int(iaq))")
|
||||
}
|
||||
.tint(gradient)
|
||||
.gaugeStyle(.accessoryCircular)
|
||||
case .gradient:
|
||||
HStack {
|
||||
Gauge(value: Double(iaq), in: 0...500) {
|
||||
Text("IAQ")
|
||||
.foregroundColor(iaqEnum.color)
|
||||
} currentValueLabel: {
|
||||
Text("IAQ ")+Text("\(Int(iaq))")
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.tint(gradient)
|
||||
.gaugeStyle(.accessoryLinear)
|
||||
Text(iaqEnum.description)
|
||||
.font(.caption)
|
||||
}
|
||||
.padding([.leading, .trailing])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct IndoorAirQuality_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
Text(".pill")
|
||||
.font(.title2)
|
||||
HStack {
|
||||
IndoorAirQuality(iaq: 6)
|
||||
IndoorAirQuality(iaq: 51)
|
||||
}
|
||||
HStack {
|
||||
IndoorAirQuality(iaq: 101)
|
||||
IndoorAirQuality(iaq: 201)
|
||||
}
|
||||
HStack {
|
||||
IndoorAirQuality(iaq: 350)
|
||||
IndoorAirQuality(iaq: 351)
|
||||
}
|
||||
Text(".dot")
|
||||
.font(.title2)
|
||||
HStack {
|
||||
IndoorAirQuality(iaq: 6, displayMode: .dot)
|
||||
IndoorAirQuality(iaq: 51, displayMode: .dot)
|
||||
IndoorAirQuality(iaq: 101, displayMode: .dot)
|
||||
IndoorAirQuality(iaq: 201, displayMode: .dot)
|
||||
IndoorAirQuality(iaq: 350, displayMode: .dot)
|
||||
IndoorAirQuality(iaq: 351, displayMode: .dot)
|
||||
}
|
||||
Text(".text")
|
||||
.font(.title2)
|
||||
HStack {
|
||||
IndoorAirQuality(iaq: 6, displayMode: .text)
|
||||
IndoorAirQuality(iaq: 51, displayMode: .text)
|
||||
IndoorAirQuality(iaq: 101, displayMode: .text)
|
||||
}
|
||||
HStack {
|
||||
IndoorAirQuality(iaq: 201, displayMode: .text)
|
||||
IndoorAirQuality(iaq: 350, displayMode: .text)
|
||||
IndoorAirQuality(iaq: 351, displayMode: .text)
|
||||
}
|
||||
Text(".gauge")
|
||||
.font(.title2)
|
||||
HStack (alignment: .top) {
|
||||
IndoorAirQuality(iaq: 6, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 51, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 101, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 151, displayMode: .gauge)
|
||||
}
|
||||
HStack (alignment: .top) {
|
||||
IndoorAirQuality(iaq: 201, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 251, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 301, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 351, displayMode: .gauge)
|
||||
}
|
||||
HStack (alignment: .top) {
|
||||
IndoorAirQuality(iaq: 401, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 500, displayMode: .gauge)
|
||||
}
|
||||
Text(".gradient")
|
||||
.font(.title2)
|
||||
IndoorAirQuality(iaq: 6, displayMode: .gradient)
|
||||
IndoorAirQuality(iaq: 51, displayMode: .gradient)
|
||||
IndoorAirQuality(iaq: 101, displayMode: .gradient)
|
||||
IndoorAirQuality(iaq: 201, displayMode: .gradient)
|
||||
IndoorAirQuality(iaq: 351, displayMode: .gradient)
|
||||
IndoorAirQuality(iaq: 401, displayMode: .gradient)
|
||||
IndoorAirQuality(iaq: 500, displayMode: .gradient)
|
||||
|
||||
}.previewLayout(.fixed(width: 300, height: 800))
|
||||
}
|
||||
}
|
||||
|
|
@ -109,6 +109,24 @@ struct ChannelList: View {
|
|||
Label("Delete Messages", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
Button {
|
||||
channel.mute = !channel.mute
|
||||
|
||||
do {
|
||||
let adminMessageId = bleManager.saveChannel(channel: channel.protoBuf, fromUser: node!.user!, toUser: node!.user!)
|
||||
if adminMessageId > 0 {
|
||||
context.refresh(channel, mergeChanges: true)
|
||||
}
|
||||
|
||||
try context.save()
|
||||
|
||||
} catch {
|
||||
context.rollback()
|
||||
print("💥 Save Channel Mute Error")
|
||||
}
|
||||
} label: {
|
||||
Label(channel.mute ? "Show Alerts" : "Hide Alerts", systemImage: channel.mute ? "bell" : "bell.slash")
|
||||
}
|
||||
}
|
||||
.confirmationDialog(
|
||||
"This conversation will be deleted.",
|
||||
|
|
|
|||
|
|
@ -43,16 +43,24 @@ struct ChannelMessageList: View {
|
|||
.padding(.trailing)
|
||||
}
|
||||
}
|
||||
HStack(alignment: .top) {
|
||||
HStack(alignment: .bottom) {
|
||||
if currentUser { Spacer(minLength: 50) }
|
||||
if !currentUser {
|
||||
CircleText(text: message.fromUser?.shortName ?? "?", color: Color(UIColor(hex: UInt32(message.fromUser?.num ?? 0))), circleSize: 44)
|
||||
.padding(.all, 5)
|
||||
.offset(y: -5)
|
||||
.offset(y: -7)
|
||||
}
|
||||
|
||||
VStack(alignment: currentUser ? .trailing : .leading) {
|
||||
let isDetectionSensorMessage = message.portNum == Int32(PortNum.detectionSensorApp.rawValue)
|
||||
|
||||
if !currentUser && message.fromUser != nil {
|
||||
Text("\(message.fromUser?.longName ?? "unknown".localized ) (\(message.fromUser?.userId ?? "?"))")
|
||||
.font(.caption)
|
||||
.foregroundColor(.gray)
|
||||
.offset(y: 8)
|
||||
}
|
||||
|
||||
HStack {
|
||||
MessageText(
|
||||
message: message,
|
||||
|
|
@ -67,7 +75,7 @@ struct ChannelMessageList: View {
|
|||
RetryButton(message: message, destination: .channel(channel))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TapbackResponses(message: message) {
|
||||
appState.unreadChannelMessages = myInfo.unreadMessages
|
||||
UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ struct DeviceConfig: View {
|
|||
@State var rebroadcastMode = 0
|
||||
@State var nodeInfoBroadcastSecs = 10800
|
||||
@State var doubleTapAsButtonPress = false
|
||||
@State var ledHeartbeatEnabled = true
|
||||
@State var isManaged = false
|
||||
@State var tzdef = ""
|
||||
|
||||
|
|
@ -58,6 +59,12 @@ struct DeviceConfig: View {
|
|||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
|
||||
Toggle(isOn: $isManaged) {
|
||||
Label("Managed Device", systemImage: "gearshape.arrow.triangle.2.circlepath")
|
||||
Text("Enabling Managed mode will restrict access to all radio configurations, such as short/long names, regions, channels, modules, etc. and will only be accessible through the Admin channel. To avoid being locked out, make sure the Admin channel is working properly before enabling it.")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
Picker("Node Info Broadcast Interval", selection: $nodeInfoBroadcastSecs ) {
|
||||
ForEach(UpdateIntervals.allCases) { ui in
|
||||
if ui.rawValue >= 3600 {
|
||||
|
|
@ -66,15 +73,18 @@ struct DeviceConfig: View {
|
|||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
}
|
||||
Section(header: Text("Hardware")) {
|
||||
|
||||
Toggle(isOn: $doubleTapAsButtonPress) {
|
||||
Label("Double Tap as Button", systemImage: "hand.tap")
|
||||
Text("Treat double tap on supported accelerometers as a user button press.")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
Toggle(isOn: $isManaged) {
|
||||
Label("Managed Device", systemImage: "gearshape.arrow.triangle.2.circlepath")
|
||||
Text("Enabling Managed mode will restrict access to all radio configurations, such as short/long names, regions, channels, modules, etc. and will only be accessible through the Admin channel. To avoid being locked out, make sure the Admin channel is working properly before enabling it.")
|
||||
Toggle(isOn: $ledHeartbeatEnabled) {
|
||||
Label("LED Heartbeat", systemImage: "waveform.path.ecg")
|
||||
Text("Controls the blinking LED on the device. For most devices this will control one of the up to 4 LEDS, the charger and GPS LEDs are not controllable.")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
|
|
@ -202,6 +212,7 @@ struct DeviceConfig: View {
|
|||
dc.doubleTapAsButtonPress = doubleTapAsButtonPress
|
||||
dc.isManaged = isManaged
|
||||
dc.tzdef = tzdef
|
||||
dc.ledHeartbeatDisabled = !ledHeartbeatEnabled
|
||||
if isManaged {
|
||||
serialEnabled = false
|
||||
debugLogEnabled = false
|
||||
|
|
@ -300,6 +311,7 @@ struct DeviceConfig: View {
|
|||
nodeInfoBroadcastSecs = 3600
|
||||
}
|
||||
self.doubleTapAsButtonPress = node?.deviceConfig?.doubleTapAsButtonPress ?? false
|
||||
self.ledHeartbeatEnabled = node?.deviceConfig?.ledHeartbeatEnabled ?? true
|
||||
self.isManaged = node?.deviceConfig?.isManaged ?? false
|
||||
if self.tzdef.isEmpty {
|
||||
self.tzdef = TimeZone.current.posixDescription
|
||||
|
|
|
|||
|
|
@ -398,21 +398,20 @@ struct MQTTConfig: View {
|
|||
if !stateTopic.isEmpty {
|
||||
nearbyTopics.append(stateTopic)
|
||||
}
|
||||
let countyTopic = defaultTopic + "/" + (placemark.subAdministrativeArea?.lowercased().replacingOccurrences(of: " ", with: "") ?? "")
|
||||
let countyTopic = defaultTopic + "/" + (placemark.administrativeArea ?? "") + "/" + (placemark.subAdministrativeArea?.lowercased().replacingOccurrences(of: " ", with: "") ?? "")
|
||||
if !countyTopic.isEmpty {
|
||||
nearbyTopics.append(countyTopic)
|
||||
}
|
||||
let cityTopic = defaultTopic + "/" + (placemark.locality?.lowercased().replacingOccurrences(of: " ", with: "") ?? "")
|
||||
let cityTopic = defaultTopic + "/" + (placemark.administrativeArea ?? "") + "/" + (placemark.locality?.lowercased().replacingOccurrences(of: " ", with: "") ?? "")
|
||||
if !cityTopic.isEmpty {
|
||||
nearbyTopics.append(cityTopic)
|
||||
}
|
||||
let neightborhoodTopic = defaultTopic + "/" + (placemark.subLocality?.lowercased()
|
||||
let neightborhoodTopic = defaultTopic + "/" + (placemark.administrativeArea ?? "") + "/" + (placemark.subLocality?.lowercased()
|
||||
.replacingOccurrences(of: " ", with: "")
|
||||
.replacingOccurrences(of: "'", with: "") ?? "")
|
||||
if !neightborhoodTopic.isEmpty {
|
||||
nearbyTopics.append(neightborhoodTopic)
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ struct Firmware: View {
|
|||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
var node: NodeInfoEntity?
|
||||
@State var minimumVersion = "2.3.5"
|
||||
@State var minimumVersion = "2.3.7"
|
||||
@State var version = ""
|
||||
@State private var currentDevice: DeviceHardware?
|
||||
@State private var latestStable: FirmwareRelease?
|
||||
|
|
|
|||
|
|
@ -6,6 +6,16 @@
|
|||
<string>Root</string>
|
||||
<key>PreferenceSpecifiers</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSTitleValueSpecifier</string>
|
||||
<key>DefaultValue</key>
|
||||
<string></string>
|
||||
<key>Title</key>
|
||||
<string>Will share your phone GPS location with your node.</string>
|
||||
<key>Key</key>
|
||||
<string>shareLocationTitle</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
|
|
@ -20,7 +30,7 @@
|
|||
<key>Type</key>
|
||||
<string>PSMultiValueSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>Share Location Interval</string>
|
||||
<string>Interval</string>
|
||||
<key>Key</key>
|
||||
<string>provideLocationInterval</string>
|
||||
<key>Values</key>
|
||||
|
|
@ -78,9 +88,9 @@
|
|||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>Low Battery</string>
|
||||
<string>Channel Messages</string>
|
||||
<key>Key</key>
|
||||
<string>lowBatteryNotifications</string>
|
||||
<string>channelMessageNotifications</string>
|
||||
<key>DefaultValue</key>
|
||||
<true/>
|
||||
</dict>
|
||||
|
|
@ -94,6 +104,16 @@
|
|||
<key>DefaultValue</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>Low Battery</string>
|
||||
<key>Key</key>
|
||||
<string>lowBatteryNotifications</string>
|
||||
<key>DefaultValue</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 86640f20db7b9b5be42949d18e8d96ad10d47a68
|
||||
Loading…
Add table
Add a link
Reference in a new issue