mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Merge pull request #579 from meshtastic/2.3.3_Working_Changes
2.3.3 working changes
This commit is contained in:
commit
76bcfa28be
46 changed files with 1042 additions and 247 deletions
|
|
@ -374,6 +374,7 @@
|
|||
DD964FC1297272AE007C176F /* WaypointEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaypointEntityExtension.swift; sourceTree = "<group>"; };
|
||||
DD964FC32974767D007C176F /* MapViewFitExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewFitExtension.swift; sourceTree = "<group>"; };
|
||||
DD964FC52975DBFD007C176F /* QueryCoreData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryCoreData.swift; sourceTree = "<group>"; };
|
||||
DD9681A22BBB22BE00FD2C47 /* MeshtasticDataModelV32.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV32.xcdatamodel; sourceTree = "<group>"; };
|
||||
DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticLogo.swift; sourceTree = "<group>"; };
|
||||
DD97E96728EFE9A00056DDA4 /* About.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = About.swift; sourceTree = "<group>"; };
|
||||
DD994B68295F88B60013760A /* IntervalEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntervalEnums.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -1610,7 +1611,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.3.2;
|
||||
MARKETING_VERSION = 2.3.3;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1644,7 +1645,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.3.2;
|
||||
MARKETING_VERSION = 2.3.3;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1766,7 +1767,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.3.2;
|
||||
MARKETING_VERSION = 2.3.3;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1799,7 +1800,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.3.2;
|
||||
MARKETING_VERSION = 2.3.3;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1910,6 +1911,7 @@
|
|||
DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
DD9681A22BBB22BE00FD2C47 /* MeshtasticDataModelV32.xcdatamodel */,
|
||||
DDDCD5712BB3246500BE6B60 /* MeshtasticDataModelV 31.xcdatamodel */,
|
||||
DD9A1A912BA2D2D3001E602E /* MeshtasticDataModelV 30.xcdatamodel */,
|
||||
DD398EBD2B93F640002B4C51 /* MeshtasticDataModelV 29.xcdatamodel */,
|
||||
|
|
@ -1942,7 +1944,7 @@
|
|||
DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */,
|
||||
DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */,
|
||||
);
|
||||
currentVersion = DDDCD5712BB3246500BE6B60 /* MeshtasticDataModelV 31.xcdatamodel */;
|
||||
currentVersion = DD9681A22BBB22BE00FD2C47 /* MeshtasticDataModelV32.xcdatamodel */;
|
||||
name = Meshtastic.xcdatamodeld;
|
||||
path = Meshtastic/Meshtastic.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"originHash" : "e9855e3a299c14a10f11ee0b8f29e4170b09548533939361223a0f50e7caac8c",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "cocoamqtt",
|
||||
|
|
@ -46,5 +47,5 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
"version" : 3
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,11 +56,12 @@ enum MeshMapDistances: Double, CaseIterable, Identifiable {
|
|||
case twoHundredMiles = 321869
|
||||
case fiveHundredMiles = 804672
|
||||
case oneThousandMiles = 1609000
|
||||
case fifteenHundredMiles = 2414016
|
||||
case twentyFiveHundredMiles = 4023360
|
||||
var id: Double { self.rawValue }
|
||||
var description: String {
|
||||
let distanceFormatter = MKDistanceFormatter()
|
||||
return "up to \(distanceFormatter.string(fromDistance: Double(self.rawValue))) away"
|
||||
return String.localizedStringWithFormat("nodelist.filter.distance %@".localized, distanceFormatter.string(fromDistance: Double(self.rawValue)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ extension MyInfoEntity {
|
|||
}
|
||||
|
||||
var unreadMessages: Int {
|
||||
let unreadMessages = messageList.filter{ ($0 as AnyObject).read == false }
|
||||
let unreadMessages = messageList.filter{ ($0 as AnyObject).read == false && ($0 as AnyObject).isEmoji == false }
|
||||
return unreadMessages.count
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,13 +14,12 @@ extension PositionEntity {
|
|||
|
||||
static func allPositionsFetchRequest() -> NSFetchRequest<PositionEntity> {
|
||||
let request: NSFetchRequest<PositionEntity> = PositionEntity.fetchRequest()
|
||||
request.fetchLimit = 200
|
||||
//request.fetchBatchSize = 1
|
||||
request.fetchLimit = 100
|
||||
request.returnsObjectsAsFaults = false
|
||||
request.includesSubentities = true
|
||||
request.returnsDistinctResults = true
|
||||
request.sortDescriptors = [NSSortDescriptor(key: "time", ascending: false)]
|
||||
let positionPredicate = NSPredicate(format: "nodePosition != nil && (nodePosition.user.shortName != nil || nodePosition.user.shortName != '') && latest == true && time >= %@", Calendar.current.date(byAdding: .day, value: -2, to: Date())! as NSDate)
|
||||
let positionPredicate = NSPredicate(format: "nodePosition != nil && (nodePosition.user.shortName != nil || nodePosition.user.shortName != '') && latest == true")
|
||||
|
||||
let pointOfInterest = LocationHelper.currentLocation
|
||||
|
||||
|
|
|
|||
|
|
@ -8,13 +8,26 @@
|
|||
import Foundation
|
||||
|
||||
extension URL {
|
||||
|
||||
func regularFileAllocatedSize() throws -> UInt64 {
|
||||
let resourceValues = try self.resourceValues(forKeys: allocatedSizeResourceKeys)
|
||||
|
||||
guard resourceValues.isRegularFile ?? false else {
|
||||
return 0
|
||||
|
||||
func regularFileAllocatedSize() throws -> UInt64 {
|
||||
let resourceValues = try self.resourceValues(forKeys: allocatedSizeResourceKeys)
|
||||
|
||||
guard resourceValues.isRegularFile ?? false else {
|
||||
return 0
|
||||
}
|
||||
return UInt64(resourceValues.totalFileAllocatedSize ?? resourceValues.fileAllocatedSize ?? 0)
|
||||
}
|
||||
subscript(queryParam: String) -> String? {
|
||||
guard let url = URLComponents(string: self.absoluteString) else { return nil }
|
||||
if let parameters = url.queryItems {
|
||||
return parameters.first(where: { $0.name == queryParam })?.value
|
||||
} else if let paramPairs = url.fragment?.components(separatedBy: "?").last?.components(separatedBy: "&") {
|
||||
for pair in paramPairs where pair.contains(queryParam) {
|
||||
return pair.components(separatedBy: "=").last
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return UInt64(resourceValues.totalFileAllocatedSize ?? resourceValues.fileAllocatedSize ?? 0)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -759,19 +759,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
if fetchedNodeInfo.count == 1 && fetchedNodeInfo[0].storeForwardConfig?.enabled == true {
|
||||
wantStoreAndForwardPackets = true;
|
||||
}
|
||||
if fetchedNodeInfo.count == 1 {
|
||||
if !(fetchedNodeInfo[0].user?.vip ?? false) {
|
||||
fetchedNodeInfo[0].user?.vip = true
|
||||
do {
|
||||
try context!.save()
|
||||
|
||||
} catch {
|
||||
context!.rollback()
|
||||
let nsError = error as NSError
|
||||
print("💥 Core Data error. Error: \(nsError)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch {
|
||||
print("Failed to find a node info for the connected node")
|
||||
|
|
@ -1317,18 +1304,36 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
return 0
|
||||
}
|
||||
|
||||
public func saveChannelSet(base64UrlString: String) -> Bool {
|
||||
public func saveChannelSet(base64UrlString: String, addChannels: Bool = false) -> Bool {
|
||||
if isConnected {
|
||||
// Before we get started delete the existing channels from the myNodeInfo
|
||||
let fetchMyInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MyInfoEntity")
|
||||
fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(connectedPeripheral.num))
|
||||
|
||||
tryClearExistingChannels()
|
||||
var i: Int32 = 0
|
||||
// Before we get started delete the existing channels from the myNodeInfo
|
||||
if !addChannels {
|
||||
tryClearExistingChannels()
|
||||
} else {
|
||||
// We are trying to add a channel so lets get the last index
|
||||
let fetchMyInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MyInfoEntity")
|
||||
fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(connectedPeripheral.num))
|
||||
do {
|
||||
let fetchedMyInfo = try context?.fetch(fetchMyInfoRequest) as? [MyInfoEntity] ?? []
|
||||
if fetchedMyInfo.count == 1 {
|
||||
if addChannels {
|
||||
i = Int32(fetchedMyInfo[0].channels?.count ?? -1)
|
||||
// Bail out if the index is negative or bigger than our max of 8
|
||||
if i < 0 || i > 8 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
print("Failed to find a node MyInfo to save these channels to")
|
||||
}
|
||||
}
|
||||
let decodedString = base64UrlString.base64urlToBase64()
|
||||
if let decodedData = Data(base64Encoded: decodedString) {
|
||||
do {
|
||||
let channelSet: ChannelSet = try ChannelSet(serializedData: decodedData)
|
||||
var i: Int32 = 0
|
||||
for cs in channelSet.settings {
|
||||
var chan = Channel()
|
||||
if i == 0 {
|
||||
|
|
@ -1385,7 +1390,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
let logString = String.localizedStringWithFormat("mesh.log.lora.config.sent %@".localized, String(connectedPeripheral.num))
|
||||
MeshLogger.log("📻 \(logString)")
|
||||
}
|
||||
return true
|
||||
|
||||
if self.connectedPeripheral != nil {
|
||||
self.sendWantConfig()
|
||||
return true
|
||||
}
|
||||
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
|
|
@ -1448,6 +1458,54 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
return false
|
||||
}
|
||||
|
||||
public func setFavoriteNode(node: NodeInfoEntity, connectedNodeNum: Int64) -> Bool {
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.setFavoriteNode = UInt32(node.num)
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(connectedNodeNum)
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.priority = MeshPacket.Priority.reliable
|
||||
meshPacket.wantAck = true
|
||||
var dataMessage = DataMessage()
|
||||
dataMessage.payload = try! adminPacket.serializedData()
|
||||
dataMessage.portnum = PortNum.adminApp
|
||||
meshPacket.decoded = dataMessage
|
||||
var toRadio: ToRadio!
|
||||
toRadio = ToRadio()
|
||||
toRadio.packet = meshPacket
|
||||
let binaryData: Data = try! toRadio.serializedData()
|
||||
|
||||
if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected{
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public func removeFavoriteNode(node: NodeInfoEntity, connectedNodeNum: Int64) -> Bool {
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.removeFavoriteNode = UInt32(node.num)
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(connectedNodeNum)
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.priority = MeshPacket.Priority.reliable
|
||||
meshPacket.wantAck = true
|
||||
var dataMessage = DataMessage()
|
||||
dataMessage.payload = try! adminPacket.serializedData()
|
||||
dataMessage.portnum = PortNum.adminApp
|
||||
meshPacket.decoded = dataMessage
|
||||
var toRadio: ToRadio!
|
||||
toRadio = ToRadio()
|
||||
toRadio.packet = meshPacket
|
||||
let binaryData: Data = try! toRadio.serializedData()
|
||||
|
||||
if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected{
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public func saveLicensedUser(ham: HamParameters, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 {
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.setHamMode = ham
|
||||
|
|
|
|||
|
|
@ -293,6 +293,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
|
|||
} else {
|
||||
let newUser = UserEntity(context: context)
|
||||
newUser.num = Int64(nodeInfo.num)
|
||||
newUser.numString = String(nodeInfo.num)
|
||||
let userId = String(format:"%2X", nodeInfo.num)
|
||||
newUser.userId = "!\(userId)"
|
||||
let last4 = String(userId.suffix(4))
|
||||
|
|
@ -357,6 +358,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
|
|||
}
|
||||
fetchedNode[0].user!.userId = nodeInfo.user.id
|
||||
fetchedNode[0].user!.num = Int64(nodeInfo.num)
|
||||
fetchedNode[0].user!.numString = String(nodeInfo.num)
|
||||
fetchedNode[0].user!.longName = nodeInfo.user.longName
|
||||
fetchedNode[0].user!.shortName = nodeInfo.user.shortName
|
||||
fetchedNode[0].user!.isLicensed = nodeInfo.user.isLicensed
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>MeshtasticDataModelV 31.xcdatamodel</string>
|
||||
<string>MeshtasticDataModelV32.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="23E224" 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="nodeInfoBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rebroadcastMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="serialEnabled" optional="YES" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="deviceConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="deviceConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DeviceHardwareEntity" representedClassName="DeviceHardwareEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="activelySupported" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="architecture" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="displayName" optional="YES" attributeType="String"/>
|
||||
<attribute name="hwModel" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="hwModelSlug" optional="YES" attributeType="String"/>
|
||||
<attribute name="platformioTarget" optional="YES" attributeType="String"/>
|
||||
</entity>
|
||||
<entity name="DeviceMetadataEntity" representedClassName="DeviceMetadataEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="canShutdown" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceStateVersion" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="firmwareVersion" optional="YES" attributeType="String"/>
|
||||
<attribute name="hasBluetooth" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasEthernet" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasWifi" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hwModel" optional="YES" attributeType="String"/>
|
||||
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="metadataNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="metadata" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DisplayConfigEntity" representedClassName="DisplayConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="compassNorthTop" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="displayMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="flipScreen" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsFormat" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="headingBold" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="oledType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="screenCarouselInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="screenOnSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="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="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<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="metricsType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="relativeHumidity" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="temperature" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="voltage" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<relationship name="nodeTelemetry" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetries" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="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>
|
||||
|
|
@ -18,6 +18,7 @@ struct MeshtasticAppleApp: App {
|
|||
@State var saveChannels = false
|
||||
@State var incomingUrl: URL?
|
||||
@State var channelSettings: String?
|
||||
@State var addChannels = false
|
||||
@StateObject var appState = AppState.shared
|
||||
|
||||
var body: some Scene {
|
||||
|
|
@ -26,7 +27,7 @@ struct MeshtasticAppleApp: App {
|
|||
.environment(\.managedObjectContext, persistenceController.container.viewContext)
|
||||
.environmentObject(bleManager)
|
||||
.sheet(isPresented: $saveChannels) {
|
||||
SaveChannelQRCode(channelSetLink: channelSettings ?? "Empty Channel URL", bleManager: bleManager)
|
||||
SaveChannelQRCode(channelSetLink: channelSettings ?? "Empty Channel URL", addChannels: addChannels, bleManager: bleManager)
|
||||
.presentationDetents([.medium, .large])
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
|
|
@ -36,9 +37,13 @@ struct MeshtasticAppleApp: App {
|
|||
self.incomingUrl = userActivity.webpageURL
|
||||
|
||||
if (self.incomingUrl?.absoluteString.lowercased().contains("meshtastic.org/e/#")) != nil {
|
||||
|
||||
if let components = self.incomingUrl?.absoluteString.components(separatedBy: "#") {
|
||||
self.channelSettings = components.last!
|
||||
guard let cs = components.last!.components(separatedBy: "?").first else {
|
||||
return
|
||||
}
|
||||
self.channelSettings = cs
|
||||
self.addChannels = Bool(self.incomingUrl?["add"] ?? "false") ?? false
|
||||
print("Add Channel \(self.addChannels)")
|
||||
}
|
||||
self.saveChannels = true
|
||||
print("User wants to open a Channel Settings URL: \(self.incomingUrl?.absoluteString ?? "No QR Code Link")")
|
||||
|
|
|
|||
|
|
@ -236,7 +236,6 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
fetchedNode[0].telemetries? = NSOrderedSet(array: newTelemetries)
|
||||
}
|
||||
if nodeInfoMessage.hasUser {
|
||||
fetchedNode[0].user!.vip = nodeInfoMessage.isFavorite
|
||||
/// Seeing Some crashes here ?
|
||||
fetchedNode[0].user!.userId = nodeInfoMessage.user.id
|
||||
fetchedNode[0].user!.num = Int64(nodeInfoMessage.num)
|
||||
|
|
|
|||
|
|
@ -350,7 +350,7 @@ struct AdminMessage {
|
|||
}
|
||||
|
||||
///
|
||||
/// Clear fixed position coordinates and then set position.fixed_position = false
|
||||
/// Clear fixed position coordinates and then set position.fixed_position = false
|
||||
var removeFixedPosition: Bool {
|
||||
get {
|
||||
if case .removeFixedPosition(let v)? = payloadVariant {return v}
|
||||
|
|
@ -547,7 +547,7 @@ struct AdminMessage {
|
|||
/// Set fixed position data on the node and then set the position.fixed_position = true
|
||||
case setFixedPosition(Position)
|
||||
///
|
||||
/// Clear fixed position coordinates and then set position.fixed_position = false
|
||||
/// Clear fixed position coordinates and then set position.fixed_position = false
|
||||
case removeFixedPosition(Bool)
|
||||
///
|
||||
/// Begins an edit transaction for config, module config, owner, and channel settings changes
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ extension MemberRole: CaseIterable {
|
|||
#endif // swift(>=4.2)
|
||||
|
||||
///
|
||||
/// Packets for the official ATAK Plugin
|
||||
/// Packets for the official ATAK Plugin
|
||||
struct TAKPacket {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
|
|
|
|||
|
|
@ -226,14 +226,14 @@ struct Config {
|
|||
///
|
||||
/// Description: Broadcasts GPS position packets as priority.
|
||||
/// Technical Details: Position Mesh packets will be prioritized higher and sent more frequently by default.
|
||||
/// When used in conjunction with power.is_power_saving = true, nodes will wake up,
|
||||
/// When used in conjunction with power.is_power_saving = true, nodes will wake up,
|
||||
/// send position, and then sleep for position.position_broadcast_secs seconds.
|
||||
case tracker // = 5
|
||||
|
||||
///
|
||||
/// Description: Broadcasts telemetry packets as priority.
|
||||
/// Technical Details: Telemetry Mesh packets will be prioritized higher and sent more frequently by default.
|
||||
/// When used in conjunction with power.is_power_saving = true, nodes will wake up,
|
||||
/// When used in conjunction with power.is_power_saving = true, nodes will wake up,
|
||||
/// send environment telemetry, and then sleep for telemetry.environment_update_interval seconds.
|
||||
case sensor // = 6
|
||||
|
||||
|
|
@ -249,12 +249,12 @@ struct Config {
|
|||
/// Technical Details: Used for nodes that "only speak when spoken to"
|
||||
/// Turns all of the routine broadcasts but allows for ad-hoc communication
|
||||
/// Still rebroadcasts, but with local only rebroadcast mode (known meshes only)
|
||||
/// Can be used for clandestine operation or to dramatically reduce airtime / power consumption
|
||||
/// Can be used for clandestine operation or to dramatically reduce airtime / power consumption
|
||||
case clientHidden // = 8
|
||||
|
||||
///
|
||||
/// Description: Broadcasts location as message to default channel regularly for to assist with device recovery.
|
||||
/// Technical Details: Used to automatically send a text message to the mesh
|
||||
/// Technical Details: Used to automatically send a text message to the mesh
|
||||
/// with the current position of the device on a frequent interval:
|
||||
/// "I'm lost! Position: lat / long"
|
||||
case lostAndFound // = 9
|
||||
|
|
|
|||
|
|
@ -1565,8 +1565,8 @@ struct MeshPacket {
|
|||
set {_uniqueStorage()._viaMqtt = newValue}
|
||||
}
|
||||
|
||||
///
|
||||
/// Hop limit with which the original packet started. Sent via LoRa using three bits in the unencrypted header.
|
||||
///
|
||||
/// Hop limit with which the original packet started. Sent via LoRa using three bits in the unencrypted header.
|
||||
/// When receiving a packet, the difference between hop_start and hop_limit gives how many hops it traveled.
|
||||
var hopStart: UInt32 {
|
||||
get {return _storage._hopStart}
|
||||
|
|
@ -2606,7 +2606,7 @@ struct DeviceMetadata {
|
|||
init() {}
|
||||
}
|
||||
|
||||
///
|
||||
///
|
||||
/// A heartbeat message is sent to the node from the client to keep the connection alive.
|
||||
/// This is currently only needed to keep serial connections alive, but can be used by any PhoneAPI.
|
||||
struct Heartbeat {
|
||||
|
|
|
|||
|
|
@ -370,7 +370,7 @@ struct Telemetry {
|
|||
}
|
||||
|
||||
///
|
||||
/// Power Metrics
|
||||
/// Power Metrics
|
||||
var powerMetrics: PowerMetrics {
|
||||
get {
|
||||
if case .powerMetrics(let v)? = variant {return v}
|
||||
|
|
@ -392,7 +392,7 @@ struct Telemetry {
|
|||
/// Air quality metrics
|
||||
case airQualityMetrics(AirQualityMetrics)
|
||||
///
|
||||
/// Power Metrics
|
||||
/// Power Metrics
|
||||
case powerMetrics(PowerMetrics)
|
||||
|
||||
#if !swift(>=4.1)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ struct ConnectedDevice: View {
|
|||
var bluetoothOn: Bool
|
||||
var deviceConnected: Bool
|
||||
var name: String
|
||||
var mqttProxyEnabled: Bool = false
|
||||
var mqttProxyConnected: Bool = false
|
||||
var phoneOnly: Bool = false
|
||||
|
||||
|
|
@ -18,11 +19,11 @@ struct ConnectedDevice: View {
|
|||
|
||||
if (phoneOnly && UIDevice.current.userInterfaceIdiom == .phone) || !phoneOnly {
|
||||
if bluetoothOn {
|
||||
if deviceConnected && mqttProxyConnected {
|
||||
if mqttProxyConnected {
|
||||
if deviceConnected && (mqttProxyEnabled || mqttProxyConnected) {
|
||||
if (mqttProxyConnected || mqttProxyEnabled) {
|
||||
Image(systemName: "iphone.gen3.radiowaves.left.and.right.circle.fill")
|
||||
.imageScale(.large)
|
||||
.foregroundColor(.green)
|
||||
.foregroundColor(mqttProxyConnected ? .green : .gray)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ func getRssiColor(rssi: Int32) -> Color {
|
|||
if rssi > -115 {
|
||||
/// Good
|
||||
return .green
|
||||
} else if rssi > -115 && rssi < -120 {
|
||||
} else if rssi > -120 {
|
||||
/// Fair
|
||||
return .yellow
|
||||
} else if rssi > -126 {
|
||||
|
|
|
|||
|
|
@ -156,7 +156,10 @@ struct ChannelMessageList: View {
|
|||
ConnectedDevice(
|
||||
bluetoothOn: bleManager.isSwitchedOn,
|
||||
deviceConnected: bleManager.connectedPeripheral != nil,
|
||||
name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?",
|
||||
mqttProxyEnabled: channel.uplinkEnabled || channel.downlinkEnabled,
|
||||
mqttProxyConnected: channel.uplinkEnabled || channel.downlinkEnabled ? bleManager.mqttProxyConnected : false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,23 +17,20 @@ struct UserList: View {
|
|||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@State private var searchText = ""
|
||||
@State private var viaLora = true
|
||||
@State private var viaMqtt = true
|
||||
@State private var isOnline = false
|
||||
@State private var isFavorite = false
|
||||
@State private var distanceFilter = false
|
||||
@State private var maxDistance: Double = 800000
|
||||
@State private var hopsAway: Int = -1
|
||||
@State private var deviceRole: Int = -1
|
||||
@State var isEditingFilters = false
|
||||
|
||||
var usersQuery: Binding<String> {
|
||||
Binding {
|
||||
searchText
|
||||
} set: { newValue in
|
||||
searchText = newValue
|
||||
/// Case Insensitive Search Text Predicates
|
||||
let searchPredicates = ["userId", "hwModel", "longName", "shortName"].map { property in
|
||||
return NSPredicate(format: "%K CONTAINS[c] %@", property, searchText)
|
||||
}
|
||||
/// Create a compound predicate using each text search predicate as an OR
|
||||
let textSearchPredicate = NSCompoundPredicate(type: .or, subpredicates: searchPredicates)
|
||||
users.nsPredicate = newValue.isEmpty ? nil : textSearchPredicate
|
||||
}
|
||||
}
|
||||
@FetchRequest(
|
||||
sortDescriptors: [NSSortDescriptor(key: "lastMessage", ascending: false), NSSortDescriptor(key: "vip", ascending: false), NSSortDescriptor(key: "longName", ascending: true)],
|
||||
sortDescriptors: [NSSortDescriptor(key: "lastMessage", ascending: false),
|
||||
NSSortDescriptor(key: "userNode.favorite", ascending: false),
|
||||
NSSortDescriptor(key: "longName", ascending: true)],
|
||||
animation: .default)
|
||||
|
||||
private var users: FetchedResults<UserEntity>
|
||||
|
|
@ -71,7 +68,7 @@ struct UserList: View {
|
|||
Text(user.longName ?? "unknown".localized)
|
||||
.font(.headline)
|
||||
Spacer()
|
||||
if user.vip {
|
||||
if (user.userNode?.favorite ?? false) {
|
||||
Image(systemName: "star.fill")
|
||||
.foregroundColor(.yellow)
|
||||
}
|
||||
|
|
@ -108,15 +105,29 @@ struct UserList: View {
|
|||
.frame(height: 62)
|
||||
.contextMenu {
|
||||
Button {
|
||||
user.vip = !user.vip
|
||||
|
||||
if node != nil && !(user.userNode?.favorite ?? false) {
|
||||
let success = bleManager.setFavoriteNode(node: user.userNode!, connectedNodeNum: Int64(node!.num))
|
||||
if success {
|
||||
user.userNode?.favorite = !(user.userNode?.favorite ?? true)
|
||||
print("Favorited a node")
|
||||
}
|
||||
} else {
|
||||
let success = bleManager.removeFavoriteNode(node: user.userNode!, connectedNodeNum: Int64(node!.num))
|
||||
if success {
|
||||
user.userNode?.favorite = !(user.userNode?.favorite ?? true)
|
||||
print("Favorited a node")
|
||||
}
|
||||
}
|
||||
context.refresh(user, mergeChanges: true)
|
||||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
context.rollback()
|
||||
print("💥 Save User VIP Error")
|
||||
print("💥 Save Node Favorite Error")
|
||||
}
|
||||
} label: {
|
||||
Label(user.vip ? "Un-Favorite" : "Favorite", systemImage: user.vip ? "star.slash.fill" : "star.fill")
|
||||
Label((user.userNode?.favorite ?? false) ? "Un-Favorite" : "Favorite", systemImage: (user.userNode?.favorite ?? false) ? "star.slash.fill" : "star.fill")
|
||||
}
|
||||
Button {
|
||||
user.mute = !user.mute
|
||||
|
|
@ -156,9 +167,142 @@ struct UserList: View {
|
|||
}
|
||||
.listStyle(.plain)
|
||||
.navigationTitle(String.localizedStringWithFormat("contacts %@".localized, String(users.count == 0 ? 0 : users.count - 1)))
|
||||
.searchable(text: usersQuery, placement: users.count > 10 ? .navigationBarDrawer(displayMode: .always) : .automatic, prompt: "Find a contact")
|
||||
.sheet(isPresented: $isEditingFilters) {
|
||||
NodeListFilter(filterTitle: "Contact Filters", viaLora: $viaLora, viaMqtt: $viaMqtt, isOnline: $isOnline, isFavorite: $isFavorite, distanceFilter: $distanceFilter, maximumDistance: $maxDistance, hopsAway: $hopsAway, deviceRole: $deviceRole)
|
||||
}
|
||||
.onChange(of: searchText) { _ in
|
||||
searchUserList()
|
||||
}
|
||||
.onChange(of: viaLora) { _ in
|
||||
if !viaLora && !viaMqtt {
|
||||
viaMqtt = true
|
||||
}
|
||||
searchUserList()
|
||||
}
|
||||
.onChange(of: viaMqtt) { _ in
|
||||
if !viaLora && !viaMqtt {
|
||||
viaLora = true
|
||||
}
|
||||
searchUserList()
|
||||
}
|
||||
.onChange(of: deviceRole) { _ in
|
||||
searchUserList()
|
||||
}
|
||||
.onChange(of: hopsAway) { _ in
|
||||
searchUserList()
|
||||
}
|
||||
.onChange(of: isOnline) { _ in
|
||||
searchUserList()
|
||||
}
|
||||
.onChange(of: isFavorite) { _ in
|
||||
searchUserList()
|
||||
}
|
||||
.onChange(of: maxDistance) { _ in
|
||||
searchUserList()
|
||||
}
|
||||
.onChange(of: distanceFilter) { _ in
|
||||
searchUserList()
|
||||
}
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
searchUserList()
|
||||
}
|
||||
.safeAreaInset(edge: .bottom, alignment: .trailing) {
|
||||
HStack {
|
||||
Button(action: {
|
||||
withAnimation {
|
||||
isEditingFilters = !isEditingFilters
|
||||
}
|
||||
}) {
|
||||
Image(systemName: !isEditingFilters ? "line.3.horizontal.decrease.circle" : "line.3.horizontal.decrease.circle.fill")
|
||||
.padding(.vertical, 5)
|
||||
}
|
||||
.tint(Color(UIColor.secondarySystemBackground))
|
||||
.foregroundColor(.accentColor)
|
||||
.buttonStyle(.borderedProminent)
|
||||
|
||||
}
|
||||
.controlSize(.regular)
|
||||
.padding(5)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
.searchable(text: $searchText, placement: users.count > 10 ? .navigationBarDrawer(displayMode: .always) : .automatic, prompt: "Find a contact")
|
||||
.disableAutocorrection(true)
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
}
|
||||
}
|
||||
|
||||
private func searchUserList() {
|
||||
|
||||
/// Case Insensitive Search Text Predicates
|
||||
let searchPredicates = ["userId", "numString", "hwModel", "longName", "shortName"].map { property in
|
||||
return NSPredicate(format: "%K CONTAINS[c] %@", property, searchText)
|
||||
}
|
||||
/// Create a compound predicate using each text search preicate as an OR
|
||||
let textSearchPredicate = NSCompoundPredicate(type: .or, subpredicates: searchPredicates)
|
||||
/// Create an array of predicates to hold our AND predicates
|
||||
var predicates: [NSPredicate] = []
|
||||
/// Mqtt
|
||||
if !(viaLora && viaMqtt) {
|
||||
if viaLora {
|
||||
let loraPredicate = NSPredicate(format: "userNode.viaMqtt == NO")
|
||||
predicates.append(loraPredicate)
|
||||
} else {
|
||||
let mqttPredicate = NSPredicate(format: "userNode.viaMqtt == YES")
|
||||
predicates.append(mqttPredicate)
|
||||
}
|
||||
}
|
||||
/// Role
|
||||
if deviceRole > -1 {
|
||||
let rolePredicate = NSPredicate(format: "role == %i", Int32(deviceRole))
|
||||
predicates.append(rolePredicate)
|
||||
}
|
||||
/// Hops Away
|
||||
if hopsAway > 0 {
|
||||
let hopsAwayPredicate = NSPredicate(format: "userNode.hopsAway == %i", Int32(hopsAway))
|
||||
predicates.append(hopsAwayPredicate)
|
||||
}
|
||||
|
||||
/// Online
|
||||
if isOnline {
|
||||
let isOnlinePredicate = NSPredicate(format: "userNode.lastHeard >= %@", Calendar.current.date(byAdding: .minute, value: -15, to: Date())! as NSDate)
|
||||
predicates.append(isOnlinePredicate)
|
||||
}
|
||||
/// Favorites
|
||||
if isFavorite {
|
||||
let isFavoritePredicate = NSPredicate(format: "userNode.favorite == YES")
|
||||
predicates.append(isFavoritePredicate)
|
||||
}
|
||||
/// Distance
|
||||
if distanceFilter {
|
||||
let pointOfInterest = LocationHelper.currentLocation
|
||||
|
||||
if pointOfInterest.latitude != LocationHelper.DefaultLocation.latitude && pointOfInterest.longitude != LocationHelper.DefaultLocation.longitude {
|
||||
let D: Double = maxDistance * 1.1
|
||||
let R: Double = 6371009
|
||||
let meanLatitidue = pointOfInterest.latitude * .pi / 180
|
||||
let deltaLatitude = D / R * 180 / .pi
|
||||
let deltaLongitude = D / (R * cos(meanLatitidue)) * 180 / .pi
|
||||
let minLatitude: Double = pointOfInterest.latitude - deltaLatitude
|
||||
let maxLatitude: Double = pointOfInterest.latitude + deltaLatitude
|
||||
let minLongitude: Double = pointOfInterest.longitude - deltaLongitude
|
||||
let maxLongitude: Double = pointOfInterest.longitude + deltaLongitude
|
||||
let distancePredicate = NSPredicate(format: "(SUBQUERY(userNode.positions, $position, $position.latest == TRUE && (%lf <= ($position.longitudeI / 1e7)) AND (($position.longitudeI / 1e7) <= %lf) AND (%lf <= ($position.latitudeI / 1e7)) AND (($position.latitudeI / 1e7) <= %lf))).@count > 0", minLongitude, maxLongitude,minLatitude, maxLatitude)
|
||||
predicates.append(distancePredicate)
|
||||
}
|
||||
}
|
||||
|
||||
if predicates.count > 0 || !searchText.isEmpty {
|
||||
if !searchText.isEmpty {
|
||||
let filterPredicates = NSCompoundPredicate(type: .and, subpredicates: predicates)
|
||||
users.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: [textSearchPredicate, filterPredicates])
|
||||
} else {
|
||||
users.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: predicates)
|
||||
}
|
||||
} else {
|
||||
users.nsPredicate = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ struct MeshMapContent: MapContent {
|
|||
}
|
||||
|
||||
/// Node History and Route Lines for favorites
|
||||
if position.nodePosition?.user?.vip ?? false {
|
||||
if position.nodePosition?.favorite ?? false {
|
||||
if showRouteLines {
|
||||
let nodePositions = Array(position.nodePosition!.positions!) as! [PositionEntity]
|
||||
let routeCoords = nodePositions.compactMap({(pos) -> CLLocationCoordinate2D in
|
||||
|
|
@ -112,7 +112,7 @@ struct MeshMapContent: MapContent {
|
|||
}
|
||||
if showNodeHistory {
|
||||
ForEach(Array(position.nodePosition!.positions!) as! [PositionEntity], id: \.self) { (mappin: PositionEntity) in
|
||||
if mappin.latest == false && mappin.nodePosition?.user?.vip ?? false {
|
||||
if mappin.latest == false && mappin.nodePosition?.favorite ?? false {
|
||||
let pf = PositionFlags(rawValue: Int(mappin.nodePosition?.metadata?.positionFlags ?? 771))
|
||||
let headingDegrees = Angle.degrees(Double(mappin.heading))
|
||||
Annotation("", coordinate: mappin.coordinate) {
|
||||
|
|
|
|||
|
|
@ -128,13 +128,13 @@ struct NodeMapContent: MapContent {
|
|||
}
|
||||
}
|
||||
}
|
||||
// .tag(position.time)
|
||||
.tag(position.time)
|
||||
.annotationTitles(.automatic)
|
||||
.annotationSubtitles(.automatic)
|
||||
}
|
||||
/// Node History
|
||||
if showNodeHistory {
|
||||
if position.latest == false && position.nodePosition?.user?.vip ?? false {
|
||||
if position.latest == false && position.nodePosition?.favorite ?? false {
|
||||
let pf = PositionFlags(rawValue: Int(position.nodePosition?.metadata?.positionFlags ?? 771))
|
||||
let headingDegrees = Angle.degrees(Double(position.heading))
|
||||
Annotation("", coordinate: position.coordinate) {
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ struct NodeMapSwiftUI: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.safeAreaInset(edge: .bottom, alignment: UIDevice.current.userInterfaceIdiom == .phone ? .leading : .trailing) {
|
||||
.safeAreaInset(edge: .bottom, alignment: .trailing) {
|
||||
HStack {
|
||||
Button(action: {
|
||||
withAnimation {
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ struct PositionPopover: View {
|
|||
Spacer()
|
||||
VStack (alignment: .center) {
|
||||
if position.nodePosition != nil {
|
||||
if position.nodePosition?.user?.vip ?? false {
|
||||
if position.nodePosition?.favorite ?? false {
|
||||
Image(systemName: "star.fill")
|
||||
.foregroundColor(.yellow)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
|
|
|
|||
|
|
@ -11,9 +11,11 @@ import SwiftUI
|
|||
struct NodeListFilter: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
/// Filters
|
||||
var filterTitle = "Node Filters"
|
||||
@Binding var viaLora: Bool
|
||||
@Binding var viaMqtt: Bool
|
||||
@Binding var isOnline: Bool
|
||||
@Binding var isFavorite: Bool
|
||||
@Binding var distanceFilter: Bool
|
||||
@Binding var maximumDistance: Double
|
||||
@Binding var hopsAway: Int
|
||||
|
|
@ -23,7 +25,7 @@ struct NodeListFilter: View {
|
|||
|
||||
NavigationStack {
|
||||
Form {
|
||||
Section(header: Text("Node Filters")) {
|
||||
Section(header: Text(filterTitle)) {
|
||||
Toggle(isOn: $viaLora) {
|
||||
|
||||
Label {
|
||||
|
|
@ -48,7 +50,7 @@ struct NodeListFilter: View {
|
|||
Toggle(isOn: $isOnline) {
|
||||
|
||||
Label {
|
||||
Text("Online Only")
|
||||
Text("Online")
|
||||
} icon: {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundColor(.green)
|
||||
|
|
@ -58,29 +60,43 @@ struct NodeListFilter: View {
|
|||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.listRowSeparator(.visible)
|
||||
|
||||
// Toggle(isOn: $distanceFilter) {
|
||||
//
|
||||
// Label {
|
||||
// Text("Distance")
|
||||
// } icon: {
|
||||
// Image(systemName: "map")
|
||||
// }
|
||||
// }
|
||||
// .toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
//
|
||||
// .listRowSeparator(distanceFilter ? .hidden : .visible)
|
||||
// if distanceFilter {
|
||||
// HStack {
|
||||
// Label("Show nodes", systemImage: "lines.measurement.horizontal")
|
||||
// Picker("", selection: $maximumDistance) {
|
||||
// ForEach(MeshMapDistances.allCases) { di in
|
||||
// Text(di.description)
|
||||
// .tag(di.id)
|
||||
// }
|
||||
// }
|
||||
// .pickerStyle(DefaultPickerStyle())
|
||||
// }
|
||||
// }
|
||||
Toggle(isOn: $isFavorite) {
|
||||
|
||||
Label {
|
||||
Text("Favorites")
|
||||
} icon: {
|
||||
|
||||
Image(systemName: "star.fill")
|
||||
.foregroundColor(.yellow)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.listRowSeparator(.visible)
|
||||
|
||||
Toggle(isOn: $distanceFilter) {
|
||||
|
||||
Label {
|
||||
Text("Distance")
|
||||
} icon: {
|
||||
Image(systemName: "map")
|
||||
}
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
.listRowSeparator(distanceFilter ? .hidden : .visible)
|
||||
if distanceFilter {
|
||||
HStack {
|
||||
Label("Show nodes", systemImage: "lines.measurement.horizontal")
|
||||
Picker("", selection: $maximumDistance) {
|
||||
ForEach(MeshMapDistances.allCases) { di in
|
||||
Text(di.description)
|
||||
.tag(di.id)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
Label("Hops Away", systemImage: "hare")
|
||||
Picker("", selection: $hopsAway) {
|
||||
|
|
@ -125,7 +141,7 @@ struct NodeListFilter: View {
|
|||
.padding(.bottom)
|
||||
#endif
|
||||
}
|
||||
.presentationDetents([.fraction(0.40), .fraction(0.50)])
|
||||
.presentationDetents([.fraction(0.6), .fraction(0.75)])
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ struct NodeListItem: View {
|
|||
Text(node.user?.longName ?? "unknown".localized)
|
||||
.fontWeight(.medium)
|
||||
.font(.headline)
|
||||
if node.user?.vip ?? false {
|
||||
if node.favorite {
|
||||
Spacer()
|
||||
Image(systemName: "star.fill")
|
||||
.foregroundColor(.yellow)
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ struct MeshMap: View {
|
|||
return
|
||||
}
|
||||
}
|
||||
.safeAreaInset(edge: .bottom, alignment: UIDevice.current.userInterfaceIdiom == .phone ? .leading : .trailing) {
|
||||
.safeAreaInset(edge: .bottom, alignment: .trailing) {
|
||||
HStack {
|
||||
Button(action: {
|
||||
withAnimation {
|
||||
|
|
@ -166,7 +166,6 @@ struct MeshMap: View {
|
|||
.padding(5)
|
||||
}
|
||||
}
|
||||
.navigationTitle("Mesh Map")
|
||||
.navigationBarItems(leading: MeshtasticLogo(), trailing: ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ struct NodeList: View {
|
|||
@State private var viaLora = true
|
||||
@State private var viaMqtt = true
|
||||
@State private var isOnline = false
|
||||
@State private var isFavorite = false
|
||||
@State private var distanceFilter = false
|
||||
@State private var maxDistance: Double = 800000
|
||||
@State private var hopsAway: Int = -1
|
||||
|
|
@ -33,7 +34,9 @@ struct NodeList: View {
|
|||
@EnvironmentObject var bleManager: BLEManager
|
||||
|
||||
@FetchRequest(
|
||||
sortDescriptors: [NSSortDescriptor(key: "user.vip", ascending: false), NSSortDescriptor(key: "lastHeard", ascending: false)],
|
||||
sortDescriptors: [NSSortDescriptor(key: "favorite", ascending: false),
|
||||
NSSortDescriptor(key: "lastHeard", ascending: false),
|
||||
NSSortDescriptor(key: "user.longName", ascending: true)],
|
||||
animation: .default)
|
||||
|
||||
var nodes: FetchedResults<NodeInfoEntity>
|
||||
|
|
@ -49,19 +52,39 @@ struct NodeList: View {
|
|||
connected: bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral?.num ?? -1 == node.num,
|
||||
connectedNode: (bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? -1 : -1))
|
||||
.contextMenu {
|
||||
if node.user != nil {
|
||||
Button {
|
||||
node.user!.vip = !node.user!.vip
|
||||
context.refresh(node, mergeChanges: true)
|
||||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
context.rollback()
|
||||
print("💥 Save User VIP Error")
|
||||
|
||||
Button {
|
||||
if !node.favorite {
|
||||
|
||||
let success = bleManager.setFavoriteNode(node: node, connectedNodeNum: Int64(connectedNodeNum))
|
||||
if success {
|
||||
node.favorite = !node.favorite
|
||||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
context.rollback()
|
||||
print("💥 Save Node Favorite Error")
|
||||
}
|
||||
print("Favorited a node")
|
||||
}
|
||||
} else {
|
||||
let success = bleManager.removeFavoriteNode(node: node, connectedNodeNum: Int64(connectedNodeNum))
|
||||
if success {
|
||||
node.favorite = !node.favorite
|
||||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
context.rollback()
|
||||
print("💥 Save Node Favorite Error")
|
||||
}
|
||||
print("Favorited a node")
|
||||
}
|
||||
} label: {
|
||||
Label(node.user?.vip ?? false ? "Un-Favorite" : "Favorite", systemImage: node.user?.vip ?? false ? "star.slash.fill" : "star.fill")
|
||||
}
|
||||
|
||||
} label: {
|
||||
Label(node.favorite ? "Un-Favorite" : "Favorite", systemImage: node.favorite ? "star.slash.fill" : "star.fill")
|
||||
}
|
||||
if node.user != nil {
|
||||
Button {
|
||||
node.user!.mute = !node.user!.mute
|
||||
context.refresh(node, mergeChanges: true)
|
||||
|
|
@ -145,7 +168,7 @@ struct NodeList: View {
|
|||
}
|
||||
}
|
||||
.sheet(isPresented: $isEditingFilters) {
|
||||
NodeListFilter(viaLora: $viaLora, viaMqtt: $viaMqtt, isOnline: $isOnline, distanceFilter: $distanceFilter, maximumDistance: $maxDistance, hopsAway: $hopsAway, deviceRole: $deviceRole)
|
||||
NodeListFilter(viaLora: $viaLora, viaMqtt: $viaMqtt, isOnline: $isOnline, isFavorite: $isFavorite, distanceFilter: $distanceFilter, maximumDistance: $maxDistance, hopsAway: $hopsAway, deviceRole: $deviceRole)
|
||||
}
|
||||
.safeAreaInset(edge: .bottom, alignment: .trailing) {
|
||||
HStack {
|
||||
|
|
@ -263,6 +286,15 @@ struct NodeList: View {
|
|||
.onChange(of: isOnline) { _ in
|
||||
searchNodeList()
|
||||
}
|
||||
.onChange(of: isFavorite) { _ in
|
||||
searchNodeList()
|
||||
}
|
||||
.onChange(of: maxDistance) { _ in
|
||||
searchNodeList()
|
||||
}
|
||||
.onChange(of: distanceFilter) { _ in
|
||||
searchNodeList()
|
||||
}
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
|
|
@ -273,7 +305,7 @@ struct NodeList: View {
|
|||
|
||||
private func searchNodeList() {
|
||||
/// Case Insensitive Search Text Predicates
|
||||
let searchPredicates = ["user.userId", "user.hwModel", "user.longName", "user.shortName"].map { property in
|
||||
let searchPredicates = ["user.userId", "user.numString", "user.hwModel", "user.longName", "user.shortName"].map { property in
|
||||
return NSPredicate(format: "%K CONTAINS[c] %@", property, searchText)
|
||||
}
|
||||
/// Create a compound predicate using each text search preicate as an OR
|
||||
|
|
@ -306,6 +338,11 @@ struct NodeList: View {
|
|||
let isOnlinePredicate = NSPredicate(format: "lastHeard >= %@", Calendar.current.date(byAdding: .minute, value: -15, to: Date())! as NSDate)
|
||||
predicates.append(isOnlinePredicate)
|
||||
}
|
||||
/// Favorites
|
||||
if isFavorite {
|
||||
let isFavoritePredicate = NSPredicate(format: "favorite == YES")
|
||||
predicates.append(isFavoritePredicate)
|
||||
}
|
||||
/// Distance
|
||||
if distanceFilter {
|
||||
let pointOfInterest = LocationHelper.currentLocation
|
||||
|
|
@ -320,15 +357,12 @@ struct NodeList: View {
|
|||
let maxLatitude: Double = pointOfInterest.latitude + deltaLatitude
|
||||
let minLongitude: Double = pointOfInterest.longitude - deltaLongitude
|
||||
let maxLongitude: Double = pointOfInterest.longitude + deltaLongitude
|
||||
let distancePredicate = NSPredicate(format: "(%lf <= (positions[first].longitudeI / 1e7))", minLongitude, maxLongitude,minLatitude, maxLatitude)
|
||||
//let distancePredicate = NSPredicate(format: "(%lf <= (positions[LAST].longitudeI / 1e7)) AND ((positions[LAST].longitudeI / 1e7) <= %lf) AND (%lf <= (positions[LAST].latitudeI / 1e7)) AND ((positions[LAST].latitudeI / 1e7) <= %lf)", minLongitude, maxLongitude,minLatitude, maxLatitude)
|
||||
|
||||
//predicates.append(distancePredicate)
|
||||
let distancePredicate = NSPredicate(format: "(SUBQUERY(positions, $position, $position.latest == TRUE && (%lf <= ($position.longitudeI / 1e7)) AND (($position.longitudeI / 1e7) <= %lf) AND (%lf <= ($position.latitudeI / 1e7)) AND (($position.latitudeI / 1e7) <= %lf))).@count > 0", minLongitude, maxLongitude,minLatitude, maxLatitude)
|
||||
predicates.append(distancePredicate)
|
||||
}
|
||||
}
|
||||
|
||||
if predicates.count > 0 || !searchText.isEmpty {
|
||||
|
||||
if !searchText.isEmpty {
|
||||
let filterPredicates = NSCompoundPredicate(type: .and, subpredicates: predicates)
|
||||
nodes.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: [textSearchPredicate, filterPredicates])
|
||||
|
|
|
|||
|
|
@ -32,21 +32,29 @@ struct Channels: View {
|
|||
@State var hasChanges = false
|
||||
@State var hasValidKey = true
|
||||
@State private var isPresentingSaveConfirm: Bool = false
|
||||
@State private var channelIndex: Int32 = 0
|
||||
@State private var channelName = ""
|
||||
@State private var channelKeySize = 16
|
||||
@State private var channelKey = "AQ=="
|
||||
@State private var channelRole = 0
|
||||
@State private var uplink = false
|
||||
@State private var downlink = false
|
||||
@State private var positionPrecision = 32.0
|
||||
@State private var preciseLocation = true
|
||||
@State private var positionsEnabled = true
|
||||
@State private var supportedVersion = true
|
||||
@State var channelIndex: Int32 = 0
|
||||
@State var channelName = ""
|
||||
@State var channelKeySize = 16
|
||||
@State var channelKey = "AQ=="
|
||||
@State var channelRole = 0
|
||||
@State var uplink = false
|
||||
@State var downlink = false
|
||||
@State var positionPrecision = 32.0
|
||||
@State var preciseLocation = true
|
||||
@State var positionsEnabled = true
|
||||
@State var supportedVersion = true
|
||||
@State var selectedChannel: ChannelEntity?
|
||||
|
||||
/// Minimum Version for granular position configuration
|
||||
@State var minimumVersion = "2.2.24"
|
||||
|
||||
@FetchRequest(
|
||||
sortDescriptors: [NSSortDescriptor(key: "favorite", ascending: false),
|
||||
NSSortDescriptor(key: "lastHeard", ascending: false),
|
||||
NSSortDescriptor(key: "user.longName", ascending: true)],
|
||||
animation: .default)
|
||||
|
||||
var nodes: FetchedResults<NodeInfoEntity>
|
||||
|
||||
var body: some View {
|
||||
|
||||
|
|
@ -134,6 +142,8 @@ struct Channels: View {
|
|||
.padding()
|
||||
#endif
|
||||
ChannelForm(channelIndex: $channelIndex, channelName: $channelName, channelKeySize: $channelKeySize, channelKey: $channelKey, channelRole: $channelRole, uplink: $uplink, downlink: $downlink, positionPrecision: $positionPrecision, preciseLocation: $preciseLocation, positionsEnabled: $positionsEnabled, hasChanges: $hasChanges, hasValidKey: $hasValidKey, supportedVersion: $supportedVersion)
|
||||
.presentationDetents([.large])
|
||||
.presentationDragIndicator(.visible)
|
||||
.onAppear {
|
||||
supportedVersion = bleManager.connectedVersion == "0.0.0" || self.minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedSame
|
||||
}
|
||||
|
|
@ -150,26 +160,24 @@ struct Channels: View {
|
|||
channel.settings.downlinkEnabled = downlink
|
||||
channel.settings.moduleSettings.positionPrecision = UInt32(positionPrecision)
|
||||
|
||||
let newChannel = ChannelEntity(context: context)
|
||||
newChannel.id = Int32(channel.index)
|
||||
newChannel.index = Int32(channel.index)
|
||||
newChannel.uplinkEnabled = channel.settings.uplinkEnabled
|
||||
newChannel.downlinkEnabled = channel.settings.downlinkEnabled
|
||||
newChannel.name = channel.settings.name
|
||||
newChannel.role = Int32(channel.role.rawValue)
|
||||
newChannel.psk = channel.settings.psk
|
||||
newChannel.positionPrecision = Int32(positionPrecision)
|
||||
selectedChannel!.role = Int32(channelRole)
|
||||
selectedChannel!.index = channelIndex
|
||||
selectedChannel!.name = channelName
|
||||
selectedChannel!.psk = Data(base64Encoded: channelKey) ?? Data()
|
||||
selectedChannel!.uplinkEnabled = uplink
|
||||
selectedChannel!.downlinkEnabled = downlink
|
||||
selectedChannel!.positionPrecision = Int32(positionPrecision)
|
||||
|
||||
guard let mutableChannels = node?.myInfo?.channels?.mutableCopy() as? NSMutableOrderedSet else {
|
||||
return
|
||||
}
|
||||
if mutableChannels.contains(newChannel) {
|
||||
mutableChannels.replaceObject(at: Int(newChannel.index), with: newChannel)
|
||||
if mutableChannels.contains(selectedChannel as Any) {
|
||||
mutableChannels.replaceObject(at: Int(channel.index), with: selectedChannel as Any)
|
||||
} else {
|
||||
mutableChannels.add(newChannel)
|
||||
mutableChannels.add(selectedChannel as Any)
|
||||
}
|
||||
node!.myInfo!.channels = mutableChannels.copy() as? NSOrderedSet
|
||||
context.refresh(newChannel, mergeChanges: true)
|
||||
node?.myInfo?.channels = mutableChannels.copy() as? NSOrderedSet
|
||||
context.refresh(selectedChannel!, mergeChanges: true)
|
||||
do {
|
||||
try context.save()
|
||||
print("💾 Saved Channel: \(channel.settings.name)")
|
||||
|
|
@ -179,24 +187,28 @@ struct Channels: View {
|
|||
print("💥 Unresolved Core Data error in the channel editor. Error: \(nsError)")
|
||||
}
|
||||
} else {
|
||||
if channelIndex <= node!.myInfo!.channels?.count ?? 0 {
|
||||
guard let channelEntity = node!.myInfo!.channels?[Int(channelIndex)] as? ChannelEntity else {
|
||||
return
|
||||
}
|
||||
let objects = channelEntity.allPrivateMessages
|
||||
for object in objects {
|
||||
context.delete(object)
|
||||
}
|
||||
context.delete(channelEntity)
|
||||
do {
|
||||
try context.save()
|
||||
print("💾 Deleted Channel: \(channel.settings.name)")
|
||||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
print("💥 Unresolved Core Data error in the channel editor. Error: \(nsError)")
|
||||
guard let channelEntity = node?.myInfo?.channels?.first(where: { ($0 as! ChannelEntity).index == channelIndex }) else {
|
||||
return
|
||||
}
|
||||
|
||||
let objects = (channelEntity as! ChannelEntity).allPrivateMessages
|
||||
for object in objects {
|
||||
context.delete(object)
|
||||
}
|
||||
for node in nodes {
|
||||
if node.channel == (channelEntity as AnyObject).index {
|
||||
context.delete(node)
|
||||
}
|
||||
}
|
||||
context.delete(channelEntity as! ChannelEntity)
|
||||
do {
|
||||
try context.save()
|
||||
print("💾 Deleted Channel: \(channel.settings.name)")
|
||||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
print("💥 Unresolved Core Data error in the channel editor. Error: \(nsError)")
|
||||
}
|
||||
}
|
||||
|
||||
let adminMessageId = bleManager.saveChannel(channel: channel, fromUser: node!.user!, toUser: node!.user!)
|
||||
|
|
@ -227,8 +239,6 @@ struct Channels: View {
|
|||
.padding(.bottom)
|
||||
#endif
|
||||
}
|
||||
.presentationDetents([.fraction(0.85), .large])
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
if node?.myInfo?.channels?.array.count ?? 0 < 8 && node != nil {
|
||||
|
||||
|
|
@ -249,7 +259,17 @@ struct Channels: View {
|
|||
uplink = false
|
||||
downlink = false
|
||||
hasChanges = true
|
||||
selectedChannel = ChannelEntity(context: context)
|
||||
|
||||
let newChannel = ChannelEntity(context: context)
|
||||
newChannel.id = channelIndex
|
||||
newChannel.index = channelIndex
|
||||
newChannel.uplinkEnabled = uplink
|
||||
newChannel.downlinkEnabled = downlink
|
||||
newChannel.name = channelName
|
||||
newChannel.role = Int32(channelRole)
|
||||
newChannel.psk = Data(base64Encoded: channelKey) ?? Data()
|
||||
newChannel.positionPrecision = Int32(positionPrecision)
|
||||
selectedChannel = newChannel
|
||||
|
||||
} label: {
|
||||
Label("Add Channel", systemImage: "plus.square")
|
||||
|
|
@ -282,7 +302,6 @@ func firstMissingChannelIndex(_ indexes: [Int]) -> Int {
|
|||
return indexes.count + 1
|
||||
}
|
||||
|
||||
|
||||
enum PositionPrecision: Int, CaseIterable, Identifiable {
|
||||
|
||||
case eleven = 11
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ struct ChannelForm: View {
|
|||
|
||||
)
|
||||
.onChange(of: channelKey, perform: { _ in
|
||||
|
||||
let tempKey = Data(base64Encoded: channelKey) ?? Data()
|
||||
if tempKey.count == channelKeySize || channelKeySize == -1{
|
||||
hasValidKey = true
|
||||
|
|
@ -245,7 +246,5 @@ struct ChannelForm: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.presentationDetents([.large])
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -289,7 +289,7 @@ struct MQTTConfig: View {
|
|||
.navigationTitle("mqtt.config")
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?", mqttProxyConnected: bleManager.mqttProxyConnected)
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?", mqttProxyEnabled: self.enabled, mqttProxyConnected: bleManager.mqttProxyConnected)
|
||||
})
|
||||
.onChange(of: address) { newAddress in
|
||||
if node != nil && node?.mqttConfig != nil {
|
||||
|
|
|
|||
|
|
@ -11,22 +11,23 @@ struct SaveChannelQRCode: View {
|
|||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
var channelSetLink: String
|
||||
var addChannels: Bool = false
|
||||
var bleManager: BLEManager
|
||||
@State var connectedToDevice = false
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("Save Channel Settings?")
|
||||
Text("\(addChannels ? "Add" : "Replace all") Channels?")
|
||||
.font(.title)
|
||||
Text("These settings will replace the current LoRa Config and Channel Settings on your radio. After everything saves your device will reboot.")
|
||||
Text("These settings will \(addChannels ? "add" : "replace all") channels. The current LoRa Config will be replaced. After everything saves your device will reboot.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.callout)
|
||||
.font(.title3)
|
||||
.padding()
|
||||
|
||||
HStack {
|
||||
|
||||
Button {
|
||||
let success = bleManager.saveChannelSet(base64UrlString: channelSetLink)
|
||||
let success = bleManager.saveChannelSet(base64UrlString: channelSetLink, addChannels: addChannels)
|
||||
if success {
|
||||
dismiss()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ import TipKit
|
|||
struct Settings: View {
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@FetchRequest(sortDescriptors: [NSSortDescriptor(key: "user.longName", ascending: true)], animation: .default)
|
||||
@FetchRequest(sortDescriptors: [NSSortDescriptor(key: "favorite", ascending: false),
|
||||
NSSortDescriptor(key: "user.longName", ascending: true)], animation: .default)
|
||||
private var nodes: FetchedResults<NodeInfoEntity>
|
||||
@State private var selectedNode: Int = 0
|
||||
@State private var preferredNodeNum: Int = 0
|
||||
|
|
@ -106,16 +107,29 @@ struct Settings: View {
|
|||
if selectedNode == 0 {
|
||||
Text("Connect to a Node").tag(0)
|
||||
}
|
||||
|
||||
ForEach(nodes) { node in
|
||||
if node.num == bleManager.connectedPeripheral?.num ?? 0 {
|
||||
Text("BLE Config: \(node.user?.longName ?? "unknown".localized)")
|
||||
.tag(Int(node.num))
|
||||
Label {
|
||||
Text("BLE: \(node.user?.longName ?? "unknown".localized)")
|
||||
} icon: {
|
||||
Image(systemName: "antenna.radiowaves.left.and.right")
|
||||
}
|
||||
.tag(Int(node.num))
|
||||
} else if node.metadata != nil {
|
||||
Text("Remote Config: \(node.user?.longName ?? "unknown".localized)")
|
||||
.tag(Int(node.num))
|
||||
Label {
|
||||
Text("Remote: \(node.user?.longName ?? "unknown".localized)")
|
||||
} icon: {
|
||||
Image(systemName: "av.remote")
|
||||
}
|
||||
.tag(Int(node.num))
|
||||
} else if hasAdmin {
|
||||
Text("Request Admin: \(node.user?.longName ?? "unknown".localized)")
|
||||
.tag(Int(node.num))
|
||||
Label {
|
||||
Text("Request Admin: \(node.user?.longName ?? "unknown".localized)")
|
||||
} icon: {
|
||||
Image(systemName: "rectangle.and.hand.point.up.left")
|
||||
}
|
||||
.tag(Int(node.num))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ struct ShareChannels: View {
|
|||
@State var includeChannel5 = true
|
||||
@State var includeChannel6 = true
|
||||
@State var includeChannel7 = true
|
||||
@State var replaceChannels = true
|
||||
var node: NodeInfoEntity?
|
||||
@State private var channelsUrl = "https://www.meshtastic.org/e/#"
|
||||
var qrCodeImage = QrCodeImage()
|
||||
|
|
@ -53,9 +54,9 @@ struct ShareChannels: View {
|
|||
var body: some View {
|
||||
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
VStack {
|
||||
TipView(ShareChannelsTip(), arrowEdge: .bottom)
|
||||
}
|
||||
// VStack {
|
||||
// TipView(ShareChannelsTip(), arrowEdge: .bottom)
|
||||
// }
|
||||
}
|
||||
GeometryReader { bounds in
|
||||
let smallest = min(bounds.size.width, bounds.size.height)
|
||||
|
|
@ -191,6 +192,17 @@ struct ShareChannels: View {
|
|||
let qrImage = qrCodeImage.generateQRCode(from: channelsUrl)
|
||||
VStack {
|
||||
if node != nil {
|
||||
Toggle(isOn: $replaceChannels) {
|
||||
Label(replaceChannels ? "Replace Channels" : "Add Channels", systemImage: replaceChannels ? "arrow.triangle.2.circlepath.circle" : "plus.app")
|
||||
}
|
||||
.tint(.accentColor)
|
||||
.toggleStyle(.button)
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding(.top)
|
||||
.padding(.bottom)
|
||||
|
||||
ShareLink("Share QR Code & Link",
|
||||
item: Image(uiImage: qrImage),
|
||||
subject: Text("Meshtastic Node \(node?.user?.shortName ?? "????") has shared channels with you"),
|
||||
|
|
@ -235,6 +247,7 @@ struct ShareChannels: View {
|
|||
.onChange(of: includeChannel5) { _ in generateChannelSet() }
|
||||
.onChange(of: includeChannel6) { _ in generateChannelSet() }
|
||||
.onChange(of: includeChannel7) { _ in generateChannelSet() }
|
||||
.onChange(of: replaceChannels) { _ in generateChannelSet() }
|
||||
}
|
||||
}
|
||||
func generateChannelSet() {
|
||||
|
|
@ -272,7 +285,7 @@ struct ShareChannels: View {
|
|||
}
|
||||
}
|
||||
let settingsString = try! channelSet.serializedData().base64EncodedString()
|
||||
channelsUrl = ("https://meshtastic.org/e/#" + settingsString.base64ToBase64url())
|
||||
channelsUrl = ("https://meshtastic.org/e/#" + settingsString.base64ToBase64url() + (replaceChannels ? "" : "?add=true"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,9 @@ SwiftUI client applications for iOS, iPadOS and macOS.
|
|||
brew install swift-protobuf
|
||||
```
|
||||
- check out the latest protobuf commit from the master branch
|
||||
```bash
|
||||
git submodule update --init
|
||||
```
|
||||
- run:
|
||||
```bash
|
||||
./gen_proto.sh
|
||||
|
|
|
|||
|
|
@ -239,6 +239,7 @@
|
|||
"network.config"="Netzwerkeinstellungen";
|
||||
"nodes"="Nodes";
|
||||
"nodes %@"="Nodes (%@)";
|
||||
"nodelist.filter.distance %@"="up to %@ away";
|
||||
"no.nodes"="Keine Meshtastic Nodes gefunden";
|
||||
"not.connected"="Kein Gerät verbunden";
|
||||
"numbers.punctuation"="Ziffern und Interpunktion";
|
||||
|
|
|
|||
|
|
@ -245,6 +245,8 @@
|
|||
"network.config"="Network Config";
|
||||
"nodes"="Nodes";
|
||||
"nodes %@"="Nodes (%@)";
|
||||
"nodelist.filter.distance %@"="up to %@ away";
|
||||
"save.config %@"="Save Config for %@";
|
||||
"no.nodes"="No Meshtastic Nodes Found";
|
||||
"not.connected"="No device connected";
|
||||
"numbers.punctuation"="Numbers and Punctuation";
|
||||
|
|
|
|||
|
|
@ -219,6 +219,7 @@
|
|||
"network.config"="Configuration du réseau";
|
||||
"nodes"="Noeuds";
|
||||
"nodes %@"="Noeuds (%@)";
|
||||
"nodelist.filter.distance %@"="up to %@ away";
|
||||
"no.nodes"="Aucun noeud Meshtastic trouvé";
|
||||
"not.connected"="Aucun appareil connecté";
|
||||
"numbers.punctuation"="Nombres and Ponctuation";
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
#!/bin/bash
|
||||
|
||||
# simple sanity checking for repo
|
||||
if [ ! -d "../protobufs" ]; then
|
||||
echo "Please check out the protobuf submodule by running: `git submodule update --init`"
|
||||
if [ ! -d "./protobufs" ]; then
|
||||
echo 'Please check out the protobuf submodule by running: `git submodule update --init`'
|
||||
exit
|
||||
fi
|
||||
|
||||
# simple sanity checking for executable
|
||||
if [ ! -x "$(which protoc)" ]; then
|
||||
echo "Please install swift-protobuf by running: brew install swift-protobuf"
|
||||
echo 'Please install swift-protobuf by running: `brew install swift-protobuf`'
|
||||
exit
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -243,6 +243,7 @@
|
|||
"network.config"="הגדרות רשת";
|
||||
"nodes"="מכשירים";
|
||||
"nodes %@"="מכשירים (%@)";
|
||||
"nodelist.filter.distance %@"="up to %@ away";
|
||||
"no.nodes"="לא נמצאו מכשירי משטסטיק";
|
||||
"not.connected"="אין מכשיר מחובר";
|
||||
"numbers.punctuation"="מספרים וסימני פיסוק ";
|
||||
|
|
|
|||
|
|
@ -240,6 +240,7 @@
|
|||
"network"="Sieć";
|
||||
"network.config"="Konfiguracja sieci";
|
||||
"nodes %@"="Węzły (%@)";
|
||||
"nodelist.filter.distance %@"="up to %@ away";
|
||||
"no.nodes"="Nie znaleziono węzłów Meshtastic";
|
||||
"not.connected"="Brak podłączonych urządzeń";
|
||||
"numbers.punctuation"="Cyfry i interpunkcja";
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit dea3a82ef2accd25112b4ef1c6f8991b579740f4
|
||||
Subproject commit e6b4c590e7c489306c9c44e3ad1fcf62a3efd288
|
||||
|
|
@ -239,6 +239,7 @@
|
|||
"network.config"="网络配置";
|
||||
"nodes"="节点";
|
||||
"nodes %@"="节点 (%@)";
|
||||
"nodelist.filter.distance %@"="up to %@ away";
|
||||
"no.nodes"="未找到 Meshtastic 节点";
|
||||
"not.connected"="未连接到电台";
|
||||
"numbers.punctuation"="数字和标点符号";
|
||||
|
|
|
|||
|
|
@ -238,6 +238,7 @@
|
|||
"network.config"="網路設定";
|
||||
"nodes"="中繼點";
|
||||
"nodes %@"="中繼點 (%@)";
|
||||
"nodelist.filter.distance %@"="up to %@ away";
|
||||
"no.nodes"="未找到 Meshtastic 中繼點";
|
||||
"not.connected"="未連接到電台";
|
||||
"numbers.punctuation"="數字和標點符號";
|
||||
|
|
|
|||
|
|
@ -23,9 +23,9 @@
|
|||
"automatic.detection"="自動識別";
|
||||
"battery.level"="電池電量";
|
||||
"ble.name"="藍芽名稱";
|
||||
"ble.connection.timeout %d %@"="嘗試連接%@失敗,你可能需要在系统設定的藍芽選項中忽略該電台。";
|
||||
"ble.errorcode.6 %@"="%@ 如果在首選電台的旁邊,App 將會自動重連。";
|
||||
"ble.errorcode.14 %@"="%@ 這個錯誤通常無法自動修復,你需要在系統設定的藍芽選項中忽略該電台並重新配對。";
|
||||
"ble.connection.timeout %d %@"="嘗試連接%@失敗,你可能需要在系统設定的藍芽選項中忽略該設備。";
|
||||
"ble.errorcode.6 %@"="%@ 如果在首選裝置的旁邊,App 將會自動重連。";
|
||||
"ble.errorcode.14 %@"="%@ 這個錯誤通常無法自動修復,你需要在系統設定的藍芽選項中忽略該裝置並重新配對。";
|
||||
"ble.errorcode.pin %@"="%@ 請再次嘗試連接並仔細檢查 PIN 碼。";
|
||||
"bluetooth"="藍芽";
|
||||
"bluetooth.off"="藍芽已關閉";
|
||||
|
|
@ -60,22 +60,22 @@
|
|||
"config.power.ls.secs"="Light Sleep Interval";
|
||||
"config.power.min.wake.secs"="最小的喚醒間隔時間";
|
||||
"config.power.saving"="省電模式";
|
||||
"config.power.saving.description"="Will sleep everything as much as possible, for the tracker and sensor role this will also include the lora radio. Don't use this setting if you want to use your device with the phone apps or are using a device without a user button.";
|
||||
"config.power.saving.description"="將會盡可能的進入休眠,追蹤器模式和感測器模式將會包含在內";
|
||||
"config.power.shutdown.on.power.loss"="失去電源後關機";
|
||||
"config.power.shutdown.after.secs"="之後";
|
||||
"config.power.wait.bluetooth.secs"="等待藍芽";
|
||||
"config.ringtone"="RTTTL Ringtone";
|
||||
"config.ringtone.title"="鈴聲";
|
||||
"config.ringtone.label"="Ringtone Transfer Language";
|
||||
"config.ringtone.description"="Ringtone Transfer Language(RTTTL) Ringtone String used by supported buzzers in external notifications.";
|
||||
"config.ringtone.description"="支援外部通知的蜂鳴器所使用的 RTTTL(Ringtone Transfer Language)鈴聲字串";
|
||||
"config.module.paxcounter.settings"="PAX Counter";
|
||||
"config.module.paxcounter.title"="PAX Counter Config";
|
||||
"config.module.paxcounter.enabled.description"="When enabled the PAX Counter module counts the number of people passing by using WiFi and Bluetooth. Both WiFI and Bluetooth must be enabled for PAX counter to work.";
|
||||
"config.module.paxcounter.updateinterval"="Update Interval";
|
||||
"config.module.paxcounter.enabled.description"="啟用 PAX 計數器模組後,將使用 WiFi 和藍牙計算經過的人數。PAX 計數器需要同時啟用 WiFi 和藍牙才能正常運作";
|
||||
"config.module.paxcounter.updateinterval"="更新間隔";
|
||||
"config.module.paxcounter.updateinterval.description"="How often we can send a message to the mesh when people are detected.";
|
||||
"config.save.confirm"="電台將會在設定儲存後重啟。";
|
||||
"connected.radio"="已連接的電台";
|
||||
"communicating"="與電台進行通訊中...";
|
||||
"config.save.confirm"="裝置將會在設定儲存後重啟。";
|
||||
"connected.radio"="已連接的裝置";
|
||||
"communicating"="與裝置進行通訊中...";
|
||||
"connected"="已連接";
|
||||
"connecting"="連接中...";
|
||||
"contacts"="聯絡人";
|
||||
|
|
@ -86,21 +86,21 @@
|
|||
"delete"="刪除";
|
||||
"detection.sensor"="檢測感測器";
|
||||
"device"="設備";
|
||||
"device.config"="電台設定";
|
||||
"device.config"="裝置設定";
|
||||
"device.configuration"="設備設定";
|
||||
"device.metrics.delete"="刪除所有電台指標??";
|
||||
"device.metrics.log"="電台指標紀錄檔";
|
||||
"device.role.client"="標準模式 - App 可以連接到電台進行收發操作,並且會自動轉發 Mesh 網路中其他中繼點的消息。";
|
||||
"device.role.clientmute"="靜音模式 - 與標準模式類似,App 可以連接到電台進行收發操作,但不會轉發 Mesh 網路中其他中繼點的消息。";
|
||||
"device.metrics.delete"="刪除所有設備指標??";
|
||||
"device.metrics.log"="設備指標紀錄檔";
|
||||
"device.role.client"="標準模式 - App 可以連接到裝置進行收發操作,並且會自動轉發 Mesh 網路中其他中繼節點的消息。";
|
||||
"device.role.clientmute"="靜音模式 - 與標準模式類似,App 可以連接到裝置進行收發操作,但不會轉發 Mesh 網路中其他中繼節點的消息。";
|
||||
"device.role.clienthidden"=" Used for nodes that \"only speak when spoken to\" Turns all of the routine broadcasts but allows for ad-hoc communication. Still rebroadcasts, but with local only rebroadcast mode (known meshes only). Can be used for private operation or to dramatically reduce airtime / power consumption.";
|
||||
"device.role.lostandfound"="Used to automatically send a text message to the mesh with the current position of the device on a frequent interval: \"I'm lost! Position: lat / long\"";
|
||||
"device.role.router"="纯路由模式 - 自動轉發 Mesh 網路中其他中繼點的消息,中繼模式下螢幕會熄滅,Wi-Fi 和藍芽將會進入睡眠模式,App 將無法連接到電台進行收發操作。";
|
||||
"device.role.routerclient"="路由客户端模式 - 優先轉發 Mesh 網路中其他中繼點的消息,App 也可以連接到電台進行收發操作。";
|
||||
"device.role.repeater"="中繼模式 - Mesh 網路數據包將優先通過此中繼點路由。此模式可消除不必要的開銷,如 NodeInfo、DeviceTelemetry 和任何其他 Mesh 數據包,從而使設備不顯示為 Mesh 網路的一部分。有關此角色的其他特定設置,請參閱轉播模式。";
|
||||
"device.role.router"="纯路由模式 - 自動轉發 Mesh 網路中其他中繼節點的消息,中繼模式下螢幕會熄滅,Wi-Fi 和藍芽將會進入睡眠模式,App 將無法連接到裝置進行收發操作。";
|
||||
"device.role.routerclient"="路由客户端模式 - 優先轉發 Mesh 網路中其他中繼節點的消息,App 也可以連接到裝置進行收發操作。";
|
||||
"device.role.repeater"="中繼模式 - Mesh 網路數據包將優先通過此中繼節點路由。此模式可消除不必要的開銷,如 NodeInfo、DeviceTelemetry 和任何其他 Mesh 數據包,從而使設備不顯示為 Mesh 網路的一部分。有關此角色的其他特定設置,請參閱轉播模式。";
|
||||
"device.role.tracker"="追蹤模式 - 用於作為 GPS 追蹤器。從該設備發送的定位數據包優先級較高,每兩分鐘廣播一次。智能位置廣播預設為關閉。";
|
||||
"direct.messages"="聊天";
|
||||
"dismiss.keyboard"="隱藏鍵盤";
|
||||
"display"="螢幕(電台螢幕)";
|
||||
"display"="螢幕(設備螢幕)";
|
||||
"display.config"="螢幕設定";
|
||||
"distance"="距離";
|
||||
"disconnect"="斷開連接";
|
||||
|
|
@ -112,7 +112,7 @@
|
|||
"external.notification.config"="外部通知設定";
|
||||
"finish"="完成";
|
||||
"firmware.version"="韌體版本";
|
||||
"firmware.version.unsupported"="檢測到不支援的韌體版本,無法連接到電台。";
|
||||
"firmware.version.unsupported"="檢測到不支援的韌體版本,無法連接到裝置。";
|
||||
"gas"="Gas";
|
||||
"gas.resistance"="Gas Resistance";
|
||||
"generate.qr.code"="生成QRcode";
|
||||
|
|
@ -165,13 +165,13 @@
|
|||
"interval.tyeight.hours"="四十八小时小時";
|
||||
"interval.eventytwo.hours"="七十二小時";
|
||||
"keyboard.type"="鍵盤類型";
|
||||
"logging"="加載中";
|
||||
"logging"="載入中";
|
||||
"lora"="LoRa";
|
||||
"lora.config"="LoRa 設定";
|
||||
"map"="Mesh 地圖";
|
||||
"map.centering"="居中";
|
||||
"map.centering"="置中";
|
||||
"map.tiles.delete"="刪除已緩存的地圖區塊";
|
||||
"map.recentering"="自動重新居中";
|
||||
"map.recentering"="自動重新置中";
|
||||
"map.use.legacy"="Use Legacy Mesh Map";
|
||||
"map.type"="地圖類型";
|
||||
"map.usertrackingmode"="使用者跟隨模式";
|
||||
|
|
@ -198,18 +198,18 @@
|
|||
"mesh.log.mqtt.config %@"="MQTT module config received: %@";
|
||||
"mesh.log.myinfo %@"="MyInfo received: %@";
|
||||
"mesh.log.network.config %@"="收到網路設定: %@";
|
||||
"mesh.log.nodeinfo.received %@"="收到中繼點訊息: %@";
|
||||
"mesh.log.nodeinfo.received %@"="收到中繼節點訊息: %@";
|
||||
"mesh.log.paxcounter %@"="PAX Counter message received for: %@";
|
||||
"mesh.log.position.config %@"="Positon config received: %@";
|
||||
"mesh.log.position.received %@"="從中繼點接收到定位封包: %@";
|
||||
"mesh.log.position.received %@"="從中繼節點接收到定位封包: %@";
|
||||
"mesh.log.rangetest.config %@"="收到拉距測試模組設定: %@";
|
||||
"mesh.log.ringtone.config %@"="RTTTL Ringtone config received: %@";
|
||||
"mesh.log.routing.message %@ %@"="Routing received for RequestID: %@ Ack Status: %@";
|
||||
"mesh.log.serial.config %@"="Serial module config received: %@";
|
||||
"mesh.log.sharelocation %@"="傳送iOS裝置的GPS定位封包到中繼點上: %@";
|
||||
"mesh.log.sharelocation %@"="傳送iOS裝置的GPS定位封包到中繼節點上: %@";
|
||||
"mesh.log.storeforward.config %@"="Store & Forward module config received: %@";
|
||||
"mesh.log.telemetry.config %@"="收到遠測模組設定: %@";
|
||||
"mesh.log.telemetry.received %@"="收到遠測資料: %@";
|
||||
"mesh.log.telemetry.config %@"="收到遙測模組設定: %@";
|
||||
"mesh.log.telemetry.received %@"="收到遙測資料: %@";
|
||||
"mesh.log.textmessage.received"="Message received from the text message app.";
|
||||
"mesh.log.textmessage.send.failed %@"="訊息傳送失敗, 沒有正確連接到 %@";
|
||||
"mesh.log.textmessage.sent %@ %@ %@"="傳送訊息 %@ 從 %@ 到 %@";
|
||||
|
|
@ -233,10 +233,10 @@
|
|||
"name"="名稱";
|
||||
"network"="網路";
|
||||
"network.config"="網路設定";
|
||||
"nodes"="中繼點";
|
||||
"nodes %@"="中繼點 (%@)";
|
||||
"no.nodes"="未找到 Meshtastic 中繼點";
|
||||
"not.connected"="未連接到電台";
|
||||
"nodes"="中繼節點";
|
||||
"nodes %@"="中繼節點 (%@)";
|
||||
"no.nodes"="未找到 Meshtastic 中繼節點";
|
||||
"not.connected"="未連接到設備";
|
||||
"numbers.punctuation"="數字和標點符號";
|
||||
"off"="關閉";
|
||||
"offline"="離線";
|
||||
|
|
@ -245,17 +245,17 @@
|
|||
"password"="密碼";
|
||||
"pause"="暫停";
|
||||
"phone.gps"="手機 GPS";
|
||||
"phone.gps.interval.description"="電台通過手機獲得定位的時間間隔,但是向 Mesh 網路中更新定位的時間間隔由電台控制。";
|
||||
"phone.gps.interval.description"="設備通過手機獲得定位的時間間隔,但是向 Mesh 網路中更新定位的時間間隔由裝置控制。";
|
||||
"position"="定位";
|
||||
"position.config"="定位設定";
|
||||
"preferred.radio"="首選電台";
|
||||
"radio.configuration"="電台設定";
|
||||
"preferred.radio"="首選設備";
|
||||
"radio.configuration"="設備設定";
|
||||
"range.test"="拉距測試";
|
||||
"range.test.blocked"="區塊範圍測試";
|
||||
"range.test.config"="拉距測試設定";
|
||||
"reply"="回復";
|
||||
"reboot"="重新啟動";
|
||||
"reboot.node"="重啟中繼點";
|
||||
"reboot.node"="重啟中繼節點";
|
||||
"received.ack"="收到確認";
|
||||
"received.ack.real"="收件人確認";
|
||||
"resume"="恢復";
|
||||
|
|
@ -272,7 +272,7 @@
|
|||
"routing.nochannel"="没有頻道";
|
||||
"routing.toolarge"="數據包過大";
|
||||
"routing.noresponse"="無回應";
|
||||
"routing.dutycyclelimit"="已達到物錢區域循環週期發射上限";
|
||||
"routing.dutycyclelimit"="已達到目前區域循環週期發射上限";
|
||||
"routing.badRequest"="錯誤請求";
|
||||
"routing.notauthorized"="未授權";
|
||||
"satellite"="衛星";
|
||||
|
|
@ -291,7 +291,7 @@
|
|||
"share.position"="分享位置";
|
||||
"subscribed"="連接到 Mesh 網路";
|
||||
"select.contact"="選擇聯絡人";
|
||||
"select.node"="選擇中繼點";
|
||||
"select.node"="選擇中繼節點";
|
||||
"select.menu.item"="從菜單選擇項目";
|
||||
"set.region"="設定 LoRa 區域";
|
||||
"standard"="標準";
|
||||
|
|
@ -303,22 +303,22 @@
|
|||
"storeforward.heartbeat"="發送心跳包";
|
||||
"tapback"="響應";
|
||||
"tapback.heart"="心";
|
||||
"tapback.thumbsup"="豎大拇指";
|
||||
"tapback.thumbsdown"="倒大拇指";
|
||||
"tapback.thumbsup"="讚";
|
||||
"tapback.thumbsdown"="倒讚";
|
||||
"tapback.haha"="哈哈";
|
||||
"tapback.exclamation"="驚嘆號";
|
||||
"tapback.question"="問號";
|
||||
"tapback.poop"="便便";
|
||||
"telemetry"="遠測(傳感器)";
|
||||
"telemetry.config"="遠側設定";
|
||||
"telemetry"="遙測(傳感器)";
|
||||
"telemetry.config"="遙測設定";
|
||||
"timeout"="超時";
|
||||
"timestamp"="時間戳記";
|
||||
"tip.bluetooth.connect.title"="連接到 LoRa 電台";
|
||||
"tip.bluetooth.connect.message"="顯示目前通過藍芽連接的 Lora 電台的信息。您可以向左滑動斷開電台,長按查看統計訊息或開始即時活動。";
|
||||
"tip.bluetooth.connect.title"="連接到 LoRa 設備";
|
||||
"tip.bluetooth.connect.message"="顯示目前通過藍芽連接的 Lora 裝置的信息。您可以向左滑動斷開裝置,長按查看統計訊息或開始即時活動。";
|
||||
"tip.channels.create.title"="管理頻道";
|
||||
"tip.channels.create.message"="現在 Mesh 上的資料會通過主通道發送。您可以設定輔助通道來建立由自己的金鑰保護的其他訊息組 [頻道設定提示](https://meshtastic.org/docs/configuration/radio/channels/)";
|
||||
"tip.channels.share.title"="共享 Meshtastic 頻道";
|
||||
"tip.channels.share.message"="在 Meshtastic 網路中最多有 8 個頻道。第一個頻道是主頻道,大多數活動都發生在這裡,也是必需的。如果您不共享主頻道,您的第一個共享頻道就會成為其他網路的主頻道。它會在其主頻道和您的輔助頻道上對話。名稱為 admin 的頻道可遠端控制中繼點。其他頻道用於私人群组,每個群組都有自己的密鑰。";
|
||||
"tip.channels.share.message"="在 Meshtastic 網路中最多有 8 個頻道。第一個頻道是主頻道,大多數活動都發生在這裡,也是必需的。如果您不共享主頻道,您的第一個共享頻道就會成為其他網路的主頻道。它會在其主頻道和您的輔助頻道上對話。名稱為 admin 的頻道可遠端控制中繼節點。其他頻道用於私人群组,每個群組都有自己的密鑰。";
|
||||
"tip.messages.title"="消息";
|
||||
"tip.messages.message"="您可以發送和接收1對1聊天和群聊。在任何訊息中,您都可以長按查看可用的操作,如複製、回復、拍一拍、刪除以及詳情。";
|
||||
"twitter"="Twitter";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue