mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Merge pull request #404 from meshtastic/2.2.8_Working_Changes
2.2.8 working changes
This commit is contained in:
commit
15d0c33bf4
16 changed files with 342 additions and 158 deletions
|
|
@ -87,6 +87,7 @@
|
|||
DD8ED9C8289CE4B900B3B0AB /* RoutingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8ED9C7289CE4B900B3B0AB /* RoutingError.swift */; };
|
||||
DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD90860D26F69BAE00DC5189 /* NodeMap.swift */; };
|
||||
DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */; };
|
||||
DD94B7402ACCE3BE00DCD1D1 /* MapSettingsForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD94B73F2ACCE3BE00DCD1D1 /* MapSettingsForm.swift */; };
|
||||
DD964FBD296E6B01007C176F /* EmojiOnlyTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FBC296E6B01007C176F /* EmojiOnlyTextField.swift */; };
|
||||
DD964FBF296E76EF007C176F /* WaypointFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FBE296E76EF007C176F /* WaypointFormView.swift */; };
|
||||
DD964FC2297272AE007C176F /* WaypointEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FC1297272AE007C176F /* WaypointEntityExtension.swift */; };
|
||||
|
|
@ -298,6 +299,7 @@
|
|||
DD90860A26F645B700DC5189 /* Meshtastic.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Meshtastic.entitlements; sourceTree = "<group>"; };
|
||||
DD90860D26F69BAE00DC5189 /* NodeMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeMap.swift; sourceTree = "<group>"; };
|
||||
DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotificationManager.swift; sourceTree = "<group>"; };
|
||||
DD94B73F2ACCE3BE00DCD1D1 /* MapSettingsForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapSettingsForm.swift; sourceTree = "<group>"; };
|
||||
DD964FBC296E6B01007C176F /* EmojiOnlyTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiOnlyTextField.swift; sourceTree = "<group>"; };
|
||||
DD964FBE296E76EF007C176F /* WaypointFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaypointFormView.swift; sourceTree = "<group>"; };
|
||||
DD964FC029724F6D007C176F /* MeshtasticDataModelV6.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV6.xcdatamodel; sourceTree = "<group>"; };
|
||||
|
|
@ -819,6 +821,7 @@
|
|||
DDDB26402AABEF7B003AFCB7 /* Helpers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DD94B73F2ACCE3BE00DCD1D1 /* MapSettingsForm.swift */,
|
||||
DDDB26432AAC0206003AFCB7 /* NodeDetail.swift */,
|
||||
DDDB26452AACC0B7003AFCB7 /* NodeInfoItem.swift */,
|
||||
DDDB26412AABF655003AFCB7 /* NodeListItem.swift */,
|
||||
|
|
@ -1140,6 +1143,7 @@
|
|||
DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */,
|
||||
DDB6ABDB28B0AC6000384BA1 /* DistanceText.swift in Sources */,
|
||||
DD5E520D298EE33B00D21B61 /* storeforward.pb.swift in Sources */,
|
||||
DD94B7402ACCE3BE00DCD1D1 /* MapSettingsForm.swift in Sources */,
|
||||
DD964FC2297272AE007C176F /* WaypointEntityExtension.swift in Sources */,
|
||||
6DA39D8E2A92DC52007E311C /* MeshtasticAppDelegate.swift in Sources */,
|
||||
DD5E520A298EE33B00D21B61 /* channel.pb.swift in Sources */,
|
||||
|
|
@ -1419,7 +1423,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.7;
|
||||
MARKETING_VERSION = 2.2.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1453,7 +1457,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.7;
|
||||
MARKETING_VERSION = 2.2.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1575,7 +1579,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.7;
|
||||
MARKETING_VERSION = 2.2.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1608,7 +1612,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.7;
|
||||
MARKETING_VERSION = 2.2.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
|
|||
|
|
@ -28,9 +28,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
@Published var isSwitchedOn: Bool = false
|
||||
@Published var automaticallyReconnect: Bool = true
|
||||
@Published var mqttProxyConnected: Bool = false
|
||||
|
||||
@StateObject var appState = AppState.shared
|
||||
//public var locationHelper = LocationHelper.shared
|
||||
public var minimumVersion = "2.0.0"
|
||||
public var connectedVersion: String
|
||||
public var isConnecting: Bool = false
|
||||
|
|
@ -43,7 +41,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
var lastPosition: CLLocationCoordinate2D?
|
||||
let emptyNodeNum: UInt32 = 4294967295
|
||||
let mqttManager = MqttClientProxyManager.shared
|
||||
//var locationHelper = LocationHelper.shared
|
||||
var wantRangeTestPackets = false
|
||||
/* Meshtastic Service Details */
|
||||
var TORADIO_characteristic: CBCharacteristic!
|
||||
|
|
@ -2073,6 +2070,72 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
return false
|
||||
}
|
||||
|
||||
func storeAndForwardPacket(packet: MeshPacket, connectedNodeNum: Int64, context: NSManagedObjectContext) {
|
||||
if let storeAndForwardMessage = try? StoreAndForward(serializedData: packet.decoded.payload) {
|
||||
// Request Response
|
||||
switch storeAndForwardMessage.rr {
|
||||
case .unset:
|
||||
MeshLogger.log("📮 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .routerError:
|
||||
MeshLogger.log("☠️ Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .routerHeartbeat:
|
||||
/// When we get a router heartbeat we know there is a store and forward node on the network
|
||||
/// Check if it is the primary S&F Router
|
||||
if (storeAndForwardMessage.heartbeat.secondary == 0) {
|
||||
/// send a request for ClientHistory with a time period matching the heartbeat
|
||||
var sfPacket = StoreAndForward()
|
||||
sfPacket.rr = StoreAndForward.RequestResponse.clientHistory
|
||||
sfPacket.history.window = storeAndForwardMessage.heartbeat.period
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(packet.from)
|
||||
meshPacket.from = 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! sfPacket.serializedData()
|
||||
dataMessage.portnum = PortNum.storeForwardApp
|
||||
dataMessage.wantResponse = true
|
||||
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)
|
||||
print("📮 Sent a request for a Store & Forward Client History to \(packet.from) for the last \(storeAndForwardMessage.heartbeat.period) seconds.")
|
||||
}
|
||||
}
|
||||
MeshLogger.log("💓 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .routerPing:
|
||||
MeshLogger.log("🏓 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .routerPong:
|
||||
MeshLogger.log("🏓 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .routerBusy:
|
||||
MeshLogger.log("🐝 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .routerHistory:
|
||||
MeshLogger.log("📜 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .routerStats:
|
||||
MeshLogger.log("📊 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .clientError:
|
||||
MeshLogger.log("☠️ Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .clientHistory:
|
||||
MeshLogger.log("📜 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .clientStats:
|
||||
MeshLogger.log("📊 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .clientPing:
|
||||
MeshLogger.log("🏓 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .clientPong:
|
||||
MeshLogger.log("🏓 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .clientAbort:
|
||||
MeshLogger.log("🛑 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .UNRECOGNIZED:
|
||||
MeshLogger.log("📮 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func tryClearExistingChannels() {
|
||||
// Before we get started delete the existing channels from the myNodeInfo
|
||||
let fetchMyInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MyInfoEntity")
|
||||
|
|
|
|||
|
|
@ -569,45 +569,6 @@ func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSMana
|
|||
}
|
||||
}
|
||||
|
||||
func storeAndForwardPacket(packet: MeshPacket, connectedNodeNum: Int64, context: NSManagedObjectContext) {
|
||||
if let storeAndForwardMessage = try? StoreAndForward(serializedData: packet.decoded.payload) {
|
||||
// Request Response
|
||||
switch storeAndForwardMessage.rr {
|
||||
case .unset:
|
||||
MeshLogger.log("📮 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .routerError:
|
||||
MeshLogger.log("☠️ Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .routerHeartbeat:
|
||||
// Query any messages since the heartbeat.period. Send their ids to the store and forward node.
|
||||
MeshLogger.log("💓 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .routerPing:
|
||||
MeshLogger.log("🏓 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .routerPong:
|
||||
MeshLogger.log("🏓 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .routerBusy:
|
||||
MeshLogger.log("🐝 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .routerHistory:
|
||||
MeshLogger.log("📜 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .routerStats:
|
||||
MeshLogger.log("📊 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .clientError:
|
||||
MeshLogger.log("☠️ Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .clientHistory:
|
||||
MeshLogger.log("📜 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .clientStats:
|
||||
MeshLogger.log("📊 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .clientPing:
|
||||
MeshLogger.log("🏓 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .clientPong:
|
||||
MeshLogger.log("🏓 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .clientAbort:
|
||||
MeshLogger.log("🛑 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
case .UNRECOGNIZED:
|
||||
MeshLogger.log("📮 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManagedObjectContext) {
|
||||
|
||||
if let telemetryMessage = try? Telemetry(serializedData: packet.decoded.payload) {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,28 @@ public func getNodeInfo(id: Int64, context: NSManagedObjectContext) -> NodeInfoE
|
|||
return nil
|
||||
}
|
||||
|
||||
public func getStoreAndForwardMessageIds(seconds: Int, context: NSManagedObjectContext) -> [UInt32] {
|
||||
|
||||
let time = seconds * -1
|
||||
let fetchMessagesRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MessageEntity")
|
||||
let timeRange = Calendar.current.date(byAdding: .minute, value: time, to: Date())
|
||||
let milleseconds = Int32(timeRange?.timeIntervalSince1970 ?? 0)
|
||||
fetchMessagesRequest.predicate = NSPredicate(format: "receivedTimestamp >= %d", milleseconds)
|
||||
|
||||
do {
|
||||
guard let fetchedMessages = try context.fetch(fetchMessagesRequest) as? [MessageEntity] else {
|
||||
return []
|
||||
}
|
||||
if fetchedMessages.count == 1 {
|
||||
return fetchedMessages.map { UInt32($0.messageId) }
|
||||
}
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
|
||||
public func getUser(id: Int64, context: NSManagedObjectContext) -> UserEntity {
|
||||
|
||||
let fetchUserRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "UserEntity")
|
||||
|
|
|
|||
|
|
@ -120,6 +120,8 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
newNode.rssi = packet.rxRssi
|
||||
if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) {
|
||||
newNode.channel = Int32(nodeInfoMessage.channel)
|
||||
print(packet.channel)
|
||||
print("Channel From Message\(nodeInfoMessage.channel)")
|
||||
}
|
||||
if let newUserMessage = try? User(serializedData: packet.decoded.payload) {
|
||||
let newUser = UserEntity(context: context)
|
||||
|
|
@ -130,6 +132,10 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
newUser.hwModel = String(describing: newUserMessage.hwModel).uppercased()
|
||||
newNode.user = newUser
|
||||
}
|
||||
|
||||
if newNode.user == nil {
|
||||
print("Nil User on nodeinfo")
|
||||
}
|
||||
|
||||
let myInfoEntity = MyInfoEntity(context: context)
|
||||
myInfoEntity.myNodeNum = Int64(packet.from)
|
||||
|
|
@ -166,6 +172,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
fetchedNode[0].telemetries? = NSOrderedSet(array: newTelemetries)
|
||||
}
|
||||
if nodeInfoMessage.hasUser {
|
||||
/// Seeing Some crashes here ?
|
||||
fetchedNode[0].user!.userId = nodeInfoMessage.user.id
|
||||
fetchedNode[0].user!.num = Int64(nodeInfoMessage.num)
|
||||
fetchedNode[0].user!.longName = nodeInfoMessage.user.longName
|
||||
|
|
@ -199,8 +206,8 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
|
||||
if let positionMessage = try? Position(serializedData: packet.decoded.payload) {
|
||||
|
||||
// Don't save empty position packets
|
||||
if positionMessage.longitudeI > 0 || positionMessage.latitudeI > 0 && (positionMessage.latitudeI != 373346000 && positionMessage.longitudeI != -1220090000) {
|
||||
/// Don't save empty position packets from null island or apple park
|
||||
if (positionMessage.longitudeI != 0 && positionMessage.latitudeI != 0) && (positionMessage.latitudeI != 373346000 && positionMessage.longitudeI != -1220090000) {
|
||||
guard let fetchedNode = try context.fetch(fetchNodePositionRequest) as? [NodeInfoEntity] else {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -227,11 +227,15 @@ struct Config {
|
|||
///
|
||||
/// Tracker device role
|
||||
/// 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,
|
||||
/// send position, and then sleep for position.position_broadcast_secs seconds.
|
||||
case tracker // = 5
|
||||
|
||||
///
|
||||
/// Sensor device role
|
||||
/// 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,
|
||||
/// send environment telemetry, and then sleep for telemetry.environment_update_interval seconds.
|
||||
case sensor // = 6
|
||||
case UNRECOGNIZED(Int)
|
||||
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ enum PortNum: SwiftProtobuf.Enum {
|
|||
|
||||
///
|
||||
/// Same as Text Message but originating from Detection Sensor Module.
|
||||
/// NOTE: This portnum traffic is not sent to the public MQTT starting at firmware version 2.2.9
|
||||
case detectionSensorApp // = 10
|
||||
|
||||
///
|
||||
|
|
@ -135,6 +136,7 @@ enum PortNum: SwiftProtobuf.Enum {
|
|||
///
|
||||
/// Optional port for messages for the range test module.
|
||||
/// ENCODING: ASCII Plaintext
|
||||
/// NOTE: This portnum traffic is not sent to the public MQTT starting at firmware version 2.2.9
|
||||
case rangeTestApp // = 66
|
||||
|
||||
///
|
||||
|
|
|
|||
|
|
@ -55,11 +55,14 @@ struct ChannelList: View {
|
|||
if channel.name?.isEmpty ?? false {
|
||||
if channel.role == 1 {
|
||||
Text(String("PrimaryChannel").camelCaseToWords())
|
||||
.font(.headline)
|
||||
} else {
|
||||
Text(String("Channel \(channel.index)").camelCaseToWords())
|
||||
.font(.headline)
|
||||
}
|
||||
} else {
|
||||
Text(String(channel.name ?? "Channel \(channel.index)").camelCaseToWords())
|
||||
.font(.headline)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
|
@ -68,19 +71,19 @@ struct ChannelList: View {
|
|||
|
||||
if lastMessageDay == currentDay {
|
||||
Text(lastMessageTime, style: .time )
|
||||
.font(.system(size: 16))
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
} else if lastMessageDay == (currentDay - 1) {
|
||||
Text("Yesterday")
|
||||
.font(.system(size: 16))
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
} else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) {
|
||||
Text(lastMessageTime.formattedDate(format: dateFormatString))
|
||||
.font(.system(size: 16))
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
} else if lastMessageDay < (currentDay - 1800) {
|
||||
Text(lastMessageTime.formattedDate(format: dateFormatString))
|
||||
.font(.system(size: 16))
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
|
|
@ -92,7 +95,8 @@ struct ChannelList: View {
|
|||
if channel.allPrivateMessages.count > 0 {
|
||||
HStack(alignment: .top) {
|
||||
Text("\(mostRecent != nil ? mostRecent!.messagePayload! : " ")")
|
||||
.font(.system(size: 16))
|
||||
//.font(.system(size: 16))
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -258,14 +258,14 @@ struct ChannelMessageList: View {
|
|||
}
|
||||
.padding([.top])
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
.onAppear(perform: {
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
if channel.allPrivateMessages.count > 0 {
|
||||
scrollView.scrollTo(channel.allPrivateMessages.last!.messageId)
|
||||
}
|
||||
})
|
||||
}
|
||||
.onChange(of: channel.allPrivateMessages, perform: { _ in
|
||||
if channel.allPrivateMessages.count > 0 {
|
||||
scrollView.scrollTo(channel.allPrivateMessages.last!.messageId)
|
||||
|
|
|
|||
|
|
@ -40,20 +40,24 @@ struct Messages: View {
|
|||
.symbolRenderingMode(.hierarchical)
|
||||
.foregroundColor(.accentColor)
|
||||
.brightness(0.2)
|
||||
.font(.title)
|
||||
Text("channels")
|
||||
.font(.title2)
|
||||
.badge(appState.unreadChannelMessages)
|
||||
.padding(.vertical)
|
||||
}
|
||||
NavigationLink {
|
||||
UserList(node: node)
|
||||
} label: {
|
||||
Image(systemName: "person")
|
||||
Image(systemName: "person.circle")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.foregroundColor(.accentColor)
|
||||
.brightness(0.2)
|
||||
.font(.largeTitle)
|
||||
Text("direct.messages")
|
||||
.font(.title2)
|
||||
.badge(appState.unreadDirectMessages)
|
||||
.padding(.vertical)
|
||||
}
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
TipView(MessagesTip(), arrowEdge: .top)
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ struct UserList: View {
|
|||
VStack(alignment: .leading){
|
||||
HStack{
|
||||
Text(user.longName ?? "unknown".localized)
|
||||
|
||||
.font(.headline)
|
||||
Spacer()
|
||||
if user.vip {
|
||||
Image(systemName: "star.fill")
|
||||
|
|
@ -72,19 +72,19 @@ struct UserList: View {
|
|||
if user.messageList.count > 0 {
|
||||
if lastMessageDay == currentDay {
|
||||
Text(lastMessageTime, style: .time )
|
||||
.font(.system(size: 16))
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
} else if lastMessageDay == (currentDay - 1) {
|
||||
Text("Yesterday")
|
||||
.font(.system(size: 16))
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
} else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) {
|
||||
Text(lastMessageTime.formattedDate(format: dateFormatString))
|
||||
.font(.system(size: 16))
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
} else if lastMessageDay < (currentDay - 1800) {
|
||||
Text(lastMessageTime.formattedDate(format: dateFormatString))
|
||||
.font(.system(size: 16))
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
|
|
@ -96,7 +96,7 @@ struct UserList: View {
|
|||
if user.messageList.count > 0 {
|
||||
HStack(alignment: .top) {
|
||||
Text("\(mostRecent != nil ? mostRecent!.messagePayload! : " ")")
|
||||
.font(.system(size: 16))
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
96
Meshtastic/Views/Nodes/Helpers/MapSettingsForm.swift
Normal file
96
Meshtastic/Views/Nodes/Helpers/MapSettingsForm.swift
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
//
|
||||
// MapSettingsForm.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Garth Vander Houwen on 10/3/23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
#if canImport(MapKit)
|
||||
import MapKit
|
||||
#endif
|
||||
|
||||
@available(iOS 17.0, macOS 14.0, *)
|
||||
struct MapSettingsForm: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@State var nodeHistory = false
|
||||
@State var routeLines = false
|
||||
@State var convexHull = false
|
||||
@State var traffic: Bool = false
|
||||
@State var pointsOfInterest: Bool = false
|
||||
@State var mapLayer: MapLayer = .standard
|
||||
|
||||
var body: some View {
|
||||
|
||||
VStack {
|
||||
Form {
|
||||
Section(header: Text("Map Options")) {
|
||||
Picker(selection: $mapLayer, label: Text("")) {
|
||||
ForEach(MapLayer.allCases, id: \.self) { layer in
|
||||
if layer != MapLayer.offline {
|
||||
Text(layer.localized)
|
||||
}
|
||||
}
|
||||
}
|
||||
.pickerStyle(SegmentedPickerStyle())
|
||||
.padding(.top, 5)
|
||||
.padding(.bottom, 5)
|
||||
Toggle(isOn: $nodeHistory) {
|
||||
Label("Node History", systemImage: "building.columns.fill")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.onTapGesture {
|
||||
self.nodeHistory.toggle()
|
||||
UserDefaults.enableMapNodeHistoryPins = self.nodeHistory
|
||||
}
|
||||
Toggle(isOn: $routeLines) {
|
||||
Label("Route Lines", systemImage: "road.lanes")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.onTapGesture {
|
||||
self.routeLines.toggle()
|
||||
UserDefaults.enableMapRouteLines = self.routeLines
|
||||
}
|
||||
Toggle(isOn: $convexHull) {
|
||||
Label("Convex Hull", systemImage: "button.angledbottom.horizontal.right")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.onTapGesture {
|
||||
self.convexHull.toggle()
|
||||
UserDefaults.enableMapConvexHull = self.convexHull
|
||||
}
|
||||
Toggle(isOn: $traffic) {
|
||||
Label("Traffic", systemImage: "car")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.onTapGesture {
|
||||
self.traffic.toggle()
|
||||
UserDefaults.enableMapTraffic = self.traffic
|
||||
}
|
||||
Toggle(isOn: $pointsOfInterest) {
|
||||
Label("Points of Interest", systemImage: "mappin.and.ellipse")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.onTapGesture {
|
||||
self.pointsOfInterest.toggle()
|
||||
UserDefaults.enableMapPointsOfInterest = self.pointsOfInterest
|
||||
}
|
||||
}
|
||||
}
|
||||
#if targetEnvironment(macCatalyst)
|
||||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
Label("close", systemImage: "xmark")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
#endif
|
||||
}
|
||||
.presentationDetents([.fraction(0.60)])
|
||||
//.presentationDetents([.medium, .large])
|
||||
.presentationDragIndicator(.automatic)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,9 @@
|
|||
|
||||
import SwiftUI
|
||||
import CoreLocation
|
||||
#if canImport(MapKit)
|
||||
import MapKit
|
||||
#endif
|
||||
import WeatherKit
|
||||
|
||||
@available(iOS 17.0, macOS 14.0, *)
|
||||
|
|
@ -22,7 +24,7 @@ struct NodeMapSwiftUI: View {
|
|||
/// Map State User Defaults
|
||||
@AppStorage("meshMapShowNodeHistory") private var showNodeHistory = false
|
||||
@AppStorage("meshMapShowRouteLines") private var showRouteLines = false
|
||||
@AppStorage("meshMapShowConvexHull") private var showConvexHull = true
|
||||
@AppStorage("enableMapConvexHull") private var showConvexHull = true
|
||||
@AppStorage("enableMapTraffic") private var showTraffic: Bool = true
|
||||
@AppStorage("enableMapPointsOfInterest") private var showPointsOfInterest: Bool = true
|
||||
@AppStorage("mapLayer") private var selectedMapLayer: MapLayer = .hybrid
|
||||
|
|
@ -69,7 +71,7 @@ struct NodeMapSwiftUI: View {
|
|||
startPoint: .leading, endPoint: .trailing
|
||||
)
|
||||
let dashed = StrokeStyle(
|
||||
lineWidth: 5,
|
||||
lineWidth: 3,
|
||||
lineCap: .round, lineJoin: .round, dash: [10, 10]
|
||||
)
|
||||
MapPolyline(coordinates: lineCoords)
|
||||
|
|
@ -80,7 +82,7 @@ struct NodeMapSwiftUI: View {
|
|||
if showConvexHull {
|
||||
let hull = lineCoords.getConvexHull()
|
||||
MapPolygon(coordinates: hull)
|
||||
.stroke(Color(nodeColor.darker()), lineWidth: 5)
|
||||
.stroke(Color(nodeColor.darker()), lineWidth: 3)
|
||||
.foregroundStyle(Color(nodeColor).opacity(0.4))
|
||||
}
|
||||
/// Waypoint Annotations
|
||||
|
|
@ -88,17 +90,17 @@ struct NodeMapSwiftUI: View {
|
|||
Annotation(waypoint.name ?? "?", coordinate: waypoint.coordinate) {
|
||||
ZStack {
|
||||
CircleText(text: String(UnicodeScalar(Int(waypoint.icon)) ?? "📍"), color: Color.orange, circleSize: 35)
|
||||
// .onTapGesture(coordinateSpace: .global) { location in
|
||||
// print("Tapped at \(location)")
|
||||
// let pinLocation = reader.convert(location, from: .local)
|
||||
// print(pinLocation)
|
||||
// let size = CGSize(width: 1, height: 50)
|
||||
// let rect = CGRect(origin: location, size: size)
|
||||
// selectedWaypointRect = rect
|
||||
// selectedWaypointPoint = location
|
||||
// showingWaypointPopover = true
|
||||
// selectedWaypoint = (selectedWaypoint == waypoint ? nil : waypoint)
|
||||
// }
|
||||
.onTapGesture(coordinateSpace: .global) { location in
|
||||
print("Tapped at \(location)")
|
||||
let pinLocation = reader.convert(location, from: .local)
|
||||
print(pinLocation)
|
||||
let size = CGSize(width: 1, height: 50)
|
||||
let rect = CGRect(origin: location, size: size)
|
||||
selectedWaypointRect = rect
|
||||
selectedWaypointPoint = location
|
||||
showingWaypointPopover = true
|
||||
selectedWaypoint = (selectedWaypoint == waypoint ? nil : waypoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -111,10 +113,11 @@ struct NodeMapSwiftUI: View {
|
|||
ZStack {
|
||||
if position.latest {
|
||||
Circle()
|
||||
.foregroundStyle(Color(nodeColor.lighter()).opacity(0.4))
|
||||
.frame(width: 60, height: 60)
|
||||
.fill(Color(nodeColor.lighter()).opacity(0.4).shadow(.drop(color: Color(nodeColor).isLight() ? .black : .white, radius: 5)))
|
||||
.foregroundStyle(Color(nodeColor.lighter()).opacity(0.3))
|
||||
.frame(width: 50, height: 50)
|
||||
if pf.contains(.Heading) {
|
||||
Image(systemName: pf.contains(.Speed) && position.speed > 1 ? "location.north" : "hexagon")
|
||||
Image(systemName: pf.contains(.Speed) && position.speed > 1 ? "location.north" : "octagon")
|
||||
.symbolEffect(.pulse.byLayer)
|
||||
.padding(5)
|
||||
.foregroundStyle(Color(nodeColor).isLight() ? .black : .white)
|
||||
|
|
@ -153,18 +156,20 @@ struct NodeMapSwiftUI: View {
|
|||
} else {
|
||||
if showNodeHistory {
|
||||
if pf.contains(.Heading) {
|
||||
Image(systemName: pf.contains(.Speed) && position.speed > 1 ? "location.north.circle" : "hexagon")
|
||||
.padding(2)
|
||||
.foregroundStyle(Color(UIColor(hex: UInt32(node.num)).lighter()).isLight() ? .black : .white)
|
||||
.background(Color(UIColor(hex: UInt32(node.num)).lighter()))
|
||||
Image(systemName: "location.north.circle")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.foregroundStyle(Color(UIColor(hex: UInt32(node.num))).isLight() ? .black : .white)
|
||||
.background(Color(UIColor(hex: UInt32(node.num))))
|
||||
.clipShape(Circle())
|
||||
.rotationEffect(headingDegrees)
|
||||
.frame(width: 16, height: 16)
|
||||
|
||||
} else {
|
||||
Image(systemName: "mappin.circle")
|
||||
.padding(2)
|
||||
.foregroundStyle(Color(UIColor(hex: UInt32(node.num)).lighter()).isLight() ? .black : .white)
|
||||
.background(Color(UIColor(hex: UInt32(node.num)).lighter()))
|
||||
.clipShape(Circle())
|
||||
Circle()
|
||||
.fill(Color(UIColor(hex: UInt32(node.num))))
|
||||
.strokeBorder(Color(UIColor(hex: UInt32(node.num))).isLight() ? .black : .white ,lineWidth: 2)
|
||||
.frame(width: 12, height: 12)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -198,13 +203,13 @@ struct NodeMapSwiftUI: View {
|
|||
.padding(.horizontal, 20)
|
||||
}
|
||||
}
|
||||
// .popover(item: $selectedWaypoint, attachmentAnchor: .rect(.rect(selectedWaypointRect)), arrowEdge: .bottom) { selection in
|
||||
// //.popover(isPresented: $showingWaypointPopover, arrowEdge: .bottom) {
|
||||
// WaypointPopover(waypoint: selection)
|
||||
// .padding()
|
||||
// .opacity(0.8)
|
||||
// .presentationCompactAdaptation(.popover)
|
||||
// }
|
||||
.popover(item: $selectedWaypoint, attachmentAnchor: .rect(.rect(selectedWaypointRect)), arrowEdge: .bottom) { selection in
|
||||
//.popover(isPresented: $showingWaypointPopover, arrowEdge: .bottom) {
|
||||
WaypointPopover(waypoint: selection)
|
||||
.padding()
|
||||
.opacity(0.8)
|
||||
.presentationCompactAdaptation(.popover)
|
||||
}
|
||||
.sheet(isPresented: $isEditingSettings) {
|
||||
VStack {
|
||||
Form {
|
||||
|
|
@ -276,7 +281,7 @@ struct NodeMapSwiftUI: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
#if targetEnvironment(macCatalyst)
|
||||
#if targetEnvironment(macCatalyst)
|
||||
Button {
|
||||
isEditingSettings = false
|
||||
} label: {
|
||||
|
|
@ -286,11 +291,11 @@ struct NodeMapSwiftUI: View {
|
|||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
.presentationDetents([.fraction(0.46)])
|
||||
//.presentationDetents([.medium])
|
||||
.presentationDragIndicator(.visible)
|
||||
.presentationDetents([.fraction(0.60)])
|
||||
//.presentationDetents([.medium, .large])
|
||||
.presentationDragIndicator(.automatic)
|
||||
}
|
||||
.onChange(of: node) {
|
||||
let mostRecent = node.positions?.lastObject as? PositionEntity
|
||||
|
|
@ -347,12 +352,12 @@ struct NodeMapSwiftUI: View {
|
|||
.buttonStyle(.borderedProminent)
|
||||
}
|
||||
|
||||
#if targetEnvironment(macCatalyst)
|
||||
#if targetEnvironment(macCatalyst)
|
||||
MapZoomStepper(scope: mapScope)
|
||||
.mapControlVisibility(.visible)
|
||||
MapPitchSlider(scope: mapScope)
|
||||
.mapControlVisibility(.visible)
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
.controlSize(.regular)
|
||||
.padding(5)
|
||||
|
|
@ -373,7 +378,7 @@ struct NodeMapSwiftUI: View {
|
|||
ContentUnavailableView("No Positions", systemImage: "mappin.slash")
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the look around scene
|
||||
private func fetchScene(for coordinate: CLLocationCoordinate2D) async throws -> MKLookAroundScene? {
|
||||
let lookAroundScene = MKLookAroundSceneRequest(coordinate: coordinate)
|
||||
return try await lookAroundScene.scene
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ struct MQTTConfig: View {
|
|||
@State var address = ""
|
||||
@State var username = ""
|
||||
@State var password = ""
|
||||
@State var encryptionEnabled = false
|
||||
@State var encryptionEnabled = true
|
||||
@State var jsonEnabled = false
|
||||
@State var tlsEnabled = true
|
||||
@State var root = "msh"
|
||||
|
|
@ -63,7 +63,7 @@ struct MQTTConfig: View {
|
|||
Label("mqtt.clientproxy", systemImage: "iphone.radiowaves.left.and.right")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Text("If both MQTT and the client proxy are enabled your device will utalize an available network connection to connect to the specified MQTT server.")
|
||||
Text("If both MQTT and the client proxy are enabled your mobile device will utalize an available network connection to connect to the specified MQTT server.")
|
||||
.font(.caption2)
|
||||
|
||||
Toggle(isOn: $encryptionEnabled) {
|
||||
|
|
@ -71,17 +71,22 @@ struct MQTTConfig: View {
|
|||
Label("Encryption Enabled", systemImage: "lock.icloud")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
Toggle(isOn: $tlsEnabled) {
|
||||
|
||||
Label("TLS Enabled", systemImage: "checkmark.shield.fill")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
Toggle(isOn: $jsonEnabled) {
|
||||
|
||||
Label("JSON Enabled", systemImage: "ellipsis.curlybraces")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Text("JSON mode is a limited, unencrypted MQTT output.")
|
||||
.font(.caption2)
|
||||
|
||||
Toggle(isOn: $tlsEnabled) {
|
||||
|
||||
Label("TLS Enabled", systemImage: "checkmark.shield.fill")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Text("Your MQTT Server must support TLS.")
|
||||
.font(.caption2)
|
||||
}
|
||||
Section(header: Text("Custom Server")) {
|
||||
HStack {
|
||||
|
|
@ -284,11 +289,18 @@ struct MQTTConfig: View {
|
|||
.onChange(of: encryptionEnabled) { newEncryptionEnabled in
|
||||
if node != nil && node?.mqttConfig != nil {
|
||||
if newEncryptionEnabled != node!.mqttConfig!.encryptionEnabled { hasChanges = true }
|
||||
if newEncryptionEnabled {
|
||||
jsonEnabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: jsonEnabled) { newJsonEnabled in
|
||||
if node != nil && node?.mqttConfig != nil {
|
||||
if newJsonEnabled != node!.mqttConfig!.jsonEnabled { hasChanges = true }
|
||||
|
||||
if newJsonEnabled {
|
||||
encryptionEnabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: tlsEnabled) { newTlsEnabled in
|
||||
|
|
|
|||
|
|
@ -36,8 +36,8 @@ struct StoreForwardConfig: View {
|
|||
|
||||
} else if node != nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 {
|
||||
// Let users know what is going on if they are using remote admin and don't have the config yet
|
||||
if node?.detectionSensorConfig == nil {
|
||||
Text("Detection Sensor config data was requested over the admin channel but no response has been returned from the remote node. You can check the status of admin message requests in the admin message log.")
|
||||
if node?.storeForwardConfig == nil {
|
||||
Text("Store and forward config data was requested over the admin channel but no response has been returned from the remote node. You can check the status of admin message requests in the admin message log.")
|
||||
.font(.callout)
|
||||
.foregroundColor(.orange)
|
||||
} else {
|
||||
|
|
@ -91,7 +91,7 @@ struct StoreForwardConfig: View {
|
|||
}
|
||||
}
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
.disabled(self.bleManager.connectedPeripheral == nil || node?.detectionSensorConfig == nil)
|
||||
.disabled(self.bleManager.connectedPeripheral == nil || node?.storeForwardConfig == nil)
|
||||
|
||||
Button {
|
||||
isPresentingSaveConfirm = true
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
"app.settings"="通用设置";
|
||||
"are.you.sure"="是否确认?";
|
||||
"ascii.capable"="ASCII Capable";
|
||||
"available.radios"="可用的电台";
|
||||
"available.radios"="可以连接的电台";
|
||||
"automatic.detection"="自动识别";
|
||||
"battery.level"="电池电量";
|
||||
"ble.name"="蓝牙名称";
|
||||
|
|
@ -37,7 +37,7 @@
|
|||
"canned.messages.config"="快捷消息配置";
|
||||
"canned.messages.preset.manual"="手动配置";
|
||||
"canned.messages.preset.rakrotary"="RAK 旋转编码器";
|
||||
"canned.messages.preset.cardkb"="M5 Stack 卡片键盘 / RAK 键盘";
|
||||
"canned.messages.preset.cardkb"="M5Stack 卡片键盘 / RAK 键盘";
|
||||
"channel"="频道";
|
||||
"channel.role.disabled"="禁用";
|
||||
"channel.role.primary"="主要";
|
||||
|
|
@ -48,7 +48,7 @@
|
|||
"clear.log"="清除日志";
|
||||
"close"="关闭";
|
||||
"config.save.confirm"="电台将会在配置保存后重启。";
|
||||
"connected.radio"="连接到电台";
|
||||
"connected.radio"="已连接的电台";
|
||||
"communicating"="与电台进行通讯中...";
|
||||
"connected"="已连接到电台";
|
||||
"connecting"="连接中...";
|
||||
|
|
@ -64,11 +64,11 @@
|
|||
"device.metrics.log"="电台指标日志";
|
||||
"device.role.client"="标准模式 - App 可以连接到电台进行收发操作,并且会自动转发 Mesh 网络中其他节点的消息。";
|
||||
"device.role.clientmute"="静默模式 - 与标准模式类似,App 可以连接到电台进行收发操作,但不会转发 Mesh 网络中其他节点的消息。";
|
||||
"device.role.router"="纯中继模式 - 自动转发 Mesh 网络中其他节点的消息,中继模式下屏幕会熄灭,Wi-Fi 和蓝牙将会进入睡眠模式,App 将无法连接到电台进行收发操作。";
|
||||
"device.role.routerclient"="中继模式 - 优先转发 Mesh 网络中其他节点的消息,App 也可以连接到电台进行收发操作。";
|
||||
"device.role.repeater"="Repeater - Mesh packets will prefer to be routed over this node. This role eliminates unnecessary overhead such as NodeInfo, DeviceTelemetry, and any other mesh packet, resulting in the device not appearing as part of the network. Please see Rebroadcast Mode for additional settings specific to this role.";
|
||||
"device.role.tracker"="Tracker - For use with devices intended as a GPS tracker. Position packets sent from this device will be higher priority, with position broadcasting every two minutes. Smart Position Broadcast will default to off.";
|
||||
"direct.messages"="直接收到的消息";
|
||||
"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.config"="屏幕配置";
|
||||
|
|
@ -94,7 +94,7 @@
|
|||
"heard"="收到";
|
||||
"heard.last"="最后收到";
|
||||
"hybrid"="混合";
|
||||
"hybrid.flyover"="Hybrid Flyover";
|
||||
"hybrid.flyover"="混合视图";
|
||||
"include"="包含";
|
||||
"inputevent.none"="无";
|
||||
"inputevent.up"="上";
|
||||
|
|
@ -114,7 +114,7 @@
|
|||
"interval.twenty.seconds"="二十秒";
|
||||
"interval.twentyfive.seconds"="二十五秒";
|
||||
"interval.thirty.seconds"="三十秒";
|
||||
"interval.fortyfive.seconds"="Forty Five Seconds";
|
||||
"interval.fortyfive.seconds"="四十五秒";
|
||||
"interval.one.minute"="一分钟";
|
||||
"interval.two.minutes"="两分钟";
|
||||
"interval.five.minutes"="五分钟";
|
||||
|
|
@ -134,19 +134,19 @@
|
|||
"interval.tyeight.hours"="四十八小时小时";
|
||||
"interval.eventytwo.hours"="七十二小时";
|
||||
"keyboard.type"="键盘类型";
|
||||
"logging"="Logging";
|
||||
"logging"="加载中";
|
||||
"lora"="LoRa";
|
||||
"lora.config"="LoRa 配置";
|
||||
"map"="Mesh 地图";
|
||||
"map.centering"="Centering";
|
||||
"map.tiles.delete"="Delete Cached Map Tiles";
|
||||
"map.recentering"="Automatic Re-centering";
|
||||
"map.centering"="居中";
|
||||
"map.tiles.delete"="删除已缓存的地图区块";
|
||||
"map.recentering"="自动重新居中";
|
||||
"map.type"="地图类型";
|
||||
"map.usertrackingmode"="User tracking mode";
|
||||
"map.usertrackingmode.none"="None";
|
||||
"map.usertrackingmode.follow"="Follow";
|
||||
"map.usertrackingmode"="用户跟随模式";
|
||||
"map.usertrackingmode.none"="无";
|
||||
"map.usertrackingmode.follow"="跟随";
|
||||
"map.usertrackingmode.followwithheading"="Follow with heading";
|
||||
"mesh.live.activity"="Mesh Live Activity";
|
||||
"mesh.live.activity"="Mesh 实时活动";
|
||||
"mesh.log"="Mesh 日志";
|
||||
"mesh.log.bluetooth.config %@"="Bluetooth config received: %@";
|
||||
"mesh.log.cannedmessage.config %@"="Canned Message module config received: %@";
|
||||
|
|
@ -192,7 +192,7 @@
|
|||
"module.configuration"="模块配置";
|
||||
"mqtt"="MQTT";
|
||||
"mqtt.config"="MQTT 配置";
|
||||
"mqtt.clientproxy"="MQTT Client Proxy";
|
||||
"mqtt.clientproxy"="MQTT 客户端代理";
|
||||
"mqtt.username"="用户名称";
|
||||
"name"="名称";
|
||||
"network"="网络";
|
||||
|
|
@ -203,27 +203,27 @@
|
|||
"not.connected"="未连接到电台";
|
||||
"numbers.punctuation"="数字和标点符号";
|
||||
"off"="关闭";
|
||||
"offline"="Offline";
|
||||
"offline"="离线";
|
||||
"on.boot"="仅在启动时";
|
||||
"options"="选项";
|
||||
"password"="密码";
|
||||
"phone.gps"="手机 GPS";
|
||||
"phone.gps.interval.description"="电台通过手机刷新定位的时间间隔,但是向 Mesh 网络中刷新定位的时间间隔由电台控制。";
|
||||
"phone.gps.interval.description"="电台通过手机获取定位的时间间隔,但是向 Mesh 网络中刷新定位的时间间隔由电台控制。";
|
||||
"position"="定位";
|
||||
"position.config"="定位配置";
|
||||
"preferred.radio"="首选电台";
|
||||
"provide.location"="提供定位到 Mesh 网络";
|
||||
"radio.configuration"="电台配置";
|
||||
"range.test"="拉距测试";
|
||||
"range.test.blocked"="Block Range Test";
|
||||
"range.test.blocked"="区块范围测试";
|
||||
"range.test.config"="拉距测试配置";
|
||||
"reply"="回复";
|
||||
"reboot"="重启";
|
||||
"reboot.node"="重启节点?";
|
||||
"received.ack"="收到确认";
|
||||
"received.ack.real"="收件人确认";
|
||||
"ringtone"="Ringtone";
|
||||
"ringtone.config"="Ringtone Config";
|
||||
"ringtone"="铃声";
|
||||
"ringtone.config"="铃声设置";
|
||||
"routing.acknowledged"="确认";
|
||||
"routing.noroute"="找不到目标";
|
||||
"routing.gotnak"="收到否认";
|
||||
|
|
@ -237,7 +237,7 @@
|
|||
"routing.badRequest"="错误请求";
|
||||
"routing.notauthorized"="未授权";
|
||||
"satellite"="卫星";
|
||||
"satellite.flyover"="Satellite Flyover";
|
||||
"satellite.flyover"="卫星视图";
|
||||
"save"="保存";
|
||||
"save.config %@"="保存%@的配置";
|
||||
"serial"="串口";
|
||||
|
|
@ -256,32 +256,32 @@
|
|||
"select.menu.item"="从菜单选择一个选项";
|
||||
"set.region"="设置 LoRa 区域";
|
||||
"standard"="标准";
|
||||
"standard.muted"="Standard Muted";
|
||||
"standard.muted"="标准静音";
|
||||
"ssid"="SSID";
|
||||
"storeforward"="Store & Forward";
|
||||
"storeforward.config"="Store & Forward Config";
|
||||
"storeforward.heartbeat"="Send Heartbeat";
|
||||
"tapback"="Tapback Response";
|
||||
"tapback.heart"="Heart";
|
||||
"tapback.thumbsup"="Thumbs Up";
|
||||
"tapback.thumbsdown"="Thumbs Down";
|
||||
"tapback.haha"="HaHa";
|
||||
"tapback.exclamation"="Exclamation Mark";
|
||||
"tapback.question"="Question Mark";
|
||||
"tapback.poop"="Poop";
|
||||
"storeforward"="储存 & 转发";
|
||||
"storeforward.config"="储存 & 转发设置";
|
||||
"storeforward.heartbeat"="发送心跳包";
|
||||
"tapback"="响应";
|
||||
"tapback.heart"="心";
|
||||
"tapback.thumbsup"="竖大拇指";
|
||||
"tapback.thumbsdown"="倒大拇指";
|
||||
"tapback.haha"="哈哈";
|
||||
"tapback.exclamation"="感叹号";
|
||||
"tapback.question"="问号";
|
||||
"tapback.poop"="便便";
|
||||
"telemetry"="遥测(传感器)";
|
||||
"telemetry.config"="遥测配置";
|
||||
"timeout"="超时";
|
||||
"timestamp"="时间戳";
|
||||
"tip.bluetooth.connect.title"="Connected LoRa Radio";
|
||||
"tip.bluetooth.connect.message"="Shows information for the Lora radio currently connected via bluetooth. You can swipe left to disconnect the radio and long press to view stats or start the live activity.";
|
||||
"tip.channels.share.title"="Sharing Meshtastic Channels";
|
||||
"tip.channels.share.message"="In a Meshtastic LoRa Mesh there are up to 8 channels. The first one is the Primary channel where most activity happens and is required. If you don't share your primary channel your first shared channel becomes the primary channel on the other network. It talks on its primary and your secondary channel. A channel with the name 'admin' controls nodes remotely. Other channels are for private groups, each with its own key.";
|
||||
"tip.messages.title"="Messages";
|
||||
"tip.messages.message"="You can send and receive channel (group chats) and direct messages. From any message you can long press to see available actions like copy, reply, tapback and delete as well as delivery details.";
|
||||
"tip.bluetooth.connect.title"="连接到 LoRa 电台";
|
||||
"tip.bluetooth.connect.message"="显示当前通过蓝牙连接的 Lora 电台的信息。您可以向左滑动断开电台,长按查看统计信息或开始实时活动。";
|
||||
"tip.channels.share.title"="共享 Meshtastic 频道";
|
||||
"tip.channels.share.message"="在 Meshtastic 网络中最多有 8 个频道。第一个频道是主频道,大多数活动都发生在这里,也是必需的。如果您不共享主频道,您的第一个共享频道就会成为其他网络的主频道。它会在其主频道和您的辅助频道上对话。名称为 admin 的频道可远程控制节点。其他频道用于私人群组,每个群组都有自己的密钥。";
|
||||
"tip.messages.title"="消息";
|
||||
"tip.messages.message"="您可以发送和接收直频消息和群聊。在任何信息中,您都可以长按查看可用的操作,如复制、回复、拍一拍、删除以及详情。";
|
||||
"twitter"="Twitter";
|
||||
"unknown"="未知";
|
||||
"unknown.age"="Unknown Age";
|
||||
"unknown.age"="未知时间";
|
||||
"unset"="未设置";
|
||||
"update.firmware"="更新你的固件";
|
||||
"update.interval"="更新间隔";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue