From c7d74f85aea9c336ef98ea555aa1f88522e764e6 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 11 Aug 2023 07:10:22 -0700 Subject: [PATCH 01/40] Updated Protos Change battery line type on device metrics chart Clean up buttons Update circle text to handle large 4 character short names. --- .../Protobufs/meshtastic/admin.pb.swift | 16 + .../Protobufs/meshtastic/config.pb.swift | 1 + .../Protobufs/meshtastic/deviceonly.pb.swift | 16 - Meshtastic/Protobufs/meshtastic/mesh.pb.swift | 756 +++++++----------- .../Protobufs/meshtastic/portnums.pb.swift | 26 + Meshtastic/Views/Bluetooth/Connect.swift | 2 +- Meshtastic/Views/Helpers/CircleText.swift | 2 +- Meshtastic/Views/Nodes/DeviceMetricsLog.swift | 6 +- .../Views/Nodes/EnvironmentMetricsLog.swift | 4 +- Meshtastic/Views/Nodes/NodeList.swift | 2 +- Meshtastic/Views/Nodes/PositionLog.swift | 4 +- Meshtastic/Views/Settings/MeshLog.swift | 11 +- 12 files changed, 361 insertions(+), 485 deletions(-) diff --git a/Meshtastic/Protobufs/meshtastic/admin.pb.swift b/Meshtastic/Protobufs/meshtastic/admin.pb.swift index 80eab93f..88ab0f6d 100644 --- a/Meshtastic/Protobufs/meshtastic/admin.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/admin.pb.swift @@ -734,6 +734,14 @@ struct AdminMessage { /// /// TODO: REPLACE case remotehardwareConfig // = 8 + + /// + /// TODO: REPLACE + case neighborinfoConfig // = 9 + + /// + /// TODO: REPLACE + case ambientlightingConfig // = 10 case UNRECOGNIZED(Int) init() { @@ -751,6 +759,8 @@ struct AdminMessage { case 6: self = .cannedmsgConfig case 7: self = .audioConfig case 8: self = .remotehardwareConfig + case 9: self = .neighborinfoConfig + case 10: self = .ambientlightingConfig default: self = .UNRECOGNIZED(rawValue) } } @@ -766,6 +776,8 @@ struct AdminMessage { case .cannedmsgConfig: return 6 case .audioConfig: return 7 case .remotehardwareConfig: return 8 + case .neighborinfoConfig: return 9 + case .ambientlightingConfig: return 10 case .UNRECOGNIZED(let i): return i } } @@ -802,6 +814,8 @@ extension AdminMessage.ModuleConfigType: CaseIterable { .cannedmsgConfig, .audioConfig, .remotehardwareConfig, + .neighborinfoConfig, + .ambientlightingConfig, ] } @@ -1412,6 +1426,8 @@ extension AdminMessage.ModuleConfigType: SwiftProtobuf._ProtoNameProviding { 6: .same(proto: "CANNEDMSG_CONFIG"), 7: .same(proto: "AUDIO_CONFIG"), 8: .same(proto: "REMOTEHARDWARE_CONFIG"), + 9: .same(proto: "NEIGHBORINFO_CONFIG"), + 10: .same(proto: "AMBIENTLIGHTING_CONFIG"), ] } diff --git a/Meshtastic/Protobufs/meshtastic/config.pb.swift b/Meshtastic/Protobufs/meshtastic/config.pb.swift index b8d6d267..2b7b5c1d 100644 --- a/Meshtastic/Protobufs/meshtastic/config.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/config.pb.swift @@ -507,6 +507,7 @@ struct Config { var waitBluetoothSecs: UInt32 = 0 /// + /// Deprecated in 2.1.X /// Mesh Super Deep Sleep Timeout Seconds /// While in Light Sleep if this value is exceeded we will lower into super deep sleep /// for sds_secs (default 1 year) or a button press diff --git a/Meshtastic/Protobufs/meshtastic/deviceonly.pb.swift b/Meshtastic/Protobufs/meshtastic/deviceonly.pb.swift index c386e36e..821b9370 100644 --- a/Meshtastic/Protobufs/meshtastic/deviceonly.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/deviceonly.pb.swift @@ -108,14 +108,6 @@ struct DeviceState { /// Clears the value of `owner`. Subsequent reads from it will return its default value. mutating func clearOwner() {_uniqueStorage()._owner = nil} - /// - /// Deprecated in 2.1.x - /// Old node_db. See NodeInfoLite node_db_lite - var nodeDb: [NodeInfo] { - get {return _storage._nodeDb} - set {_uniqueStorage()._nodeDb = newValue} - } - /// /// Received packets saved for delivery to the phone var receiveQueue: [MeshPacket] { @@ -446,7 +438,6 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 2: .standard(proto: "my_node"), 3: .same(proto: "owner"), - 4: .standard(proto: "node_db"), 5: .standard(proto: "receive_queue"), 8: .same(proto: "version"), 7: .standard(proto: "rx_text_message"), @@ -460,7 +451,6 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati fileprivate class _StorageClass { var _myNode: MyNodeInfo? = nil var _owner: User? = nil - var _nodeDb: [NodeInfo] = [] var _receiveQueue: [MeshPacket] = [] var _version: UInt32 = 0 var _rxTextMessage: MeshPacket? = nil @@ -477,7 +467,6 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati init(copying source: _StorageClass) { _myNode = source._myNode _owner = source._owner - _nodeDb = source._nodeDb _receiveQueue = source._receiveQueue _version = source._version _rxTextMessage = source._rxTextMessage @@ -506,7 +495,6 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati switch fieldNumber { case 2: try { try decoder.decodeSingularMessageField(value: &_storage._myNode) }() case 3: try { try decoder.decodeSingularMessageField(value: &_storage._owner) }() - case 4: try { try decoder.decodeRepeatedMessageField(value: &_storage._nodeDb) }() case 5: try { try decoder.decodeRepeatedMessageField(value: &_storage._receiveQueue) }() case 7: try { try decoder.decodeSingularMessageField(value: &_storage._rxTextMessage) }() case 8: try { try decoder.decodeSingularUInt32Field(value: &_storage._version) }() @@ -533,9 +521,6 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati try { if let v = _storage._owner { try visitor.visitSingularMessageField(value: v, fieldNumber: 3) } }() - if !_storage._nodeDb.isEmpty { - try visitor.visitRepeatedMessageField(value: _storage._nodeDb, fieldNumber: 4) - } if !_storage._receiveQueue.isEmpty { try visitor.visitRepeatedMessageField(value: _storage._receiveQueue, fieldNumber: 5) } @@ -571,7 +556,6 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati let rhs_storage = _args.1 if _storage._myNode != rhs_storage._myNode {return false} if _storage._owner != rhs_storage._owner {return false} - if _storage._nodeDb != rhs_storage._nodeDb {return false} if _storage._receiveQueue != rhs_storage._receiveQueue {return false} if _storage._version != rhs_storage._version {return false} if _storage._rxTextMessage != rhs_storage._rxTextMessage {return false} diff --git a/Meshtastic/Protobufs/meshtastic/mesh.pb.swift b/Meshtastic/Protobufs/meshtastic/mesh.pb.swift index fbbbd0a5..3b759f18 100644 --- a/Meshtastic/Protobufs/meshtastic/mesh.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/mesh.pb.swift @@ -189,13 +189,17 @@ enum HardwareModel: SwiftProtobuf.Enum { case heltecWirelessPaper // = 49 /// - /// LilyGo T-Deck with ESP32-S3 CPU, Keyboard, and IPS display + /// LilyGo T-Deck with ESP32-S3 CPU, Keyboard and IPS display case tDeck // = 50 /// /// LilyGo T-Watch S3 with ESP32-S3 CPU and IPS display case tWatchS3 // = 51 + /// + /// Bobricius Picomputer with ESP32-S3 CPU, Keyboard and IPS display + case picomputerS3 // = 52 + /// /// ------------------------------------------------------------------------------------------------------------------------------------------ /// Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. @@ -250,6 +254,7 @@ enum HardwareModel: SwiftProtobuf.Enum { case 49: self = .heltecWirelessPaper case 50: self = .tDeck case 51: self = .tWatchS3 + case 52: self = .picomputerS3 case 255: self = .privateHw default: self = .UNRECOGNIZED(rawValue) } @@ -298,6 +303,7 @@ enum HardwareModel: SwiftProtobuf.Enum { case .heltecWirelessPaper: return 49 case .tDeck: return 50 case .tWatchS3: return 51 + case .picomputerS3: return 52 case .privateHw: return 255 case .UNRECOGNIZED(let i): return i } @@ -351,6 +357,7 @@ extension HardwareModel: CaseIterable { .heltecWirelessPaper, .tDeck, .tWatchS3, + .picomputerS3, .privateHw, ] } @@ -1498,9 +1505,9 @@ struct MeshPacket { /// to make sure that critical packets are sent ASAP. /// In the case of meshtastic that means we want to send protocol acks as soon as possible /// (to prevent unneeded retransmissions), we want routing messages to be sent next, - /// then messages marked as reliable and finally ‘background’ packets like periodic position updates. + /// then messages marked as reliable and finally 'background' packets like periodic position updates. /// So I bit the bullet and implemented a new (internal - not sent over the air) - /// field in MeshPacket called ‘priority’. + /// field in MeshPacket called 'priority'. /// And the transmission queue in the router object is now a priority queue. enum Priority: SwiftProtobuf.Enum { typealias RawValue = Int @@ -1738,88 +1745,16 @@ struct MyNodeInfo { /// lowbyte of macaddr, but it will be fixed if that is already in use var myNodeNum: UInt32 = 0 - /// - /// Deprecated in 2.1.x (Source from device_metadata) - /// Note: This flag merely means we detected a hardware GPS in our node. - /// Not the same as UserPreferences.location_sharing - var hasGps_p: Bool = false - - /// - /// Deprecated in 2.1.x - /// The maximum number of 'software' channels that can be set on this node. - var maxChannels: UInt32 = 0 - - /// - /// Deprecated in 2.1.x (Source from device_metadata) - /// 0.0.5 etc... - var firmwareVersion: String = String() - - /// - /// An error message we'd like to report back to the mothership through analytics. - /// It indicates a serious bug occurred on the device, the device coped with it, - /// but we still want to tell the devs about the bug. - /// This field will be cleared after the phone reads MyNodeInfo - /// (i.e. it will only be reported once) - /// a numeric error code to go with error message, zero means no error - var errorCode: CriticalErrorCode = .none - - /// - /// A numeric error address (nonzero if available) - var errorAddress: UInt32 = 0 - - /// - /// The total number of errors this node has ever encountered - /// (well - since the last time we discarded preferences) - var errorCount: UInt32 = 0 - /// /// The total number of reboots this node has ever encountered /// (well - since the last time we discarded preferences) var rebootCount: UInt32 = 0 - /// - /// Deprecated in 2.1.x - /// Calculated bitrate of the current channel (in Bytes Per Second) - var bitrate: Float = 0 - - /// - /// Deprecated in 2.1.x - /// How long before we consider a message abandoned and we can clear our - /// caches of any messages in flight Normally quite large to handle the worst case - /// message delivery time, 5 minutes. - /// Formerly called FLOOD_EXPIRE_TIME in the device code - var messageTimeoutMsec: UInt32 = 0 - /// /// The minimum app version that can talk to this device. /// Phone/PC apps should compare this to their build number and if too low tell the user they must update their app var minAppVersion: UInt32 = 0 - /// - /// Deprecated in 2.1.x (Only used on device to keep track of utilization) - /// 24 time windows of 1hr each with the airtime transmitted out of the device per hour. - var airPeriodTx: [UInt32] = [] - - /// - /// Deprecated in 2.1.x (Only used on device to keep track of utilization) - /// 24 time windows of 1hr each with the airtime of valid packets for your mesh. - var airPeriodRx: [UInt32] = [] - - /// - /// Deprecated in 2.1.x (Source from DeviceMetadata instead) - /// Is the device wifi capable? - var hasWifi_p: Bool = false - - /// - /// Deprecated in 2.1.x (Source from DeviceMetrics telemetry payloads) - /// Utilization for the current channel, including well formed TX, RX and malformed RX (aka noise). - var channelUtilization: Float = 0 - - /// - /// Deprecated in 2.1.x (Source from DeviceMetrics telemetry payloads) - /// Percent of airtime for transmission used within the last hour. - var airUtilTx: Float = 0 - var unknownFields = SwiftProtobuf.UnknownStorage() init() {} @@ -1975,26 +1910,20 @@ struct FromRadio { /// /// The packet id, used to allow the phone to request missing read packets from the FIFO, /// see our bluetooth docs - var id: UInt32 { - get {return _storage._id} - set {_uniqueStorage()._id = newValue} - } + var id: UInt32 = 0 /// /// Log levels, chosen to match python logging conventions. - var payloadVariant: OneOf_PayloadVariant? { - get {return _storage._payloadVariant} - set {_uniqueStorage()._payloadVariant = newValue} - } + var payloadVariant: FromRadio.OneOf_PayloadVariant? = nil /// /// Log levels, chosen to match python logging conventions. var packet: MeshPacket { get { - if case .packet(let v)? = _storage._payloadVariant {return v} + if case .packet(let v)? = payloadVariant {return v} return MeshPacket() } - set {_uniqueStorage()._payloadVariant = .packet(newValue)} + set {payloadVariant = .packet(newValue)} } /// @@ -2002,10 +1931,10 @@ struct FromRadio { /// NOTE: This ID must not change - to keep (minimal) compatibility with <1.2 version of android apps. var myInfo: MyNodeInfo { get { - if case .myInfo(let v)? = _storage._payloadVariant {return v} + if case .myInfo(let v)? = payloadVariant {return v} return MyNodeInfo() } - set {_uniqueStorage()._payloadVariant = .myInfo(newValue)} + set {payloadVariant = .myInfo(newValue)} } /// @@ -2013,30 +1942,30 @@ struct FromRadio { /// starts over with the first node in our DB var nodeInfo: NodeInfo { get { - if case .nodeInfo(let v)? = _storage._payloadVariant {return v} + if case .nodeInfo(let v)? = payloadVariant {return v} return NodeInfo() } - set {_uniqueStorage()._payloadVariant = .nodeInfo(newValue)} + set {payloadVariant = .nodeInfo(newValue)} } /// /// Include a part of the config (was: RadioConfig radio) var config: Config { get { - if case .config(let v)? = _storage._payloadVariant {return v} + if case .config(let v)? = payloadVariant {return v} return Config() } - set {_uniqueStorage()._payloadVariant = .config(newValue)} + set {payloadVariant = .config(newValue)} } /// /// Set to send debug console output over our protobuf stream var logRecord: LogRecord { get { - if case .logRecord(let v)? = _storage._payloadVariant {return v} + if case .logRecord(let v)? = payloadVariant {return v} return LogRecord() } - set {_uniqueStorage()._payloadVariant = .logRecord(newValue)} + set {payloadVariant = .logRecord(newValue)} } /// @@ -2046,10 +1975,10 @@ struct FromRadio { /// NOTE: This ID must not change - to keep (minimal) compatibility with <1.2 version of android apps. var configCompleteID: UInt32 { get { - if case .configCompleteID(let v)? = _storage._payloadVariant {return v} + if case .configCompleteID(let v)? = payloadVariant {return v} return 0 } - set {_uniqueStorage()._payloadVariant = .configCompleteID(newValue)} + set {payloadVariant = .configCompleteID(newValue)} } /// @@ -2059,70 +1988,70 @@ struct FromRadio { /// NOTE: This ID must not change - to keep (minimal) compatibility with <1.2 version of android apps. var rebooted: Bool { get { - if case .rebooted(let v)? = _storage._payloadVariant {return v} + if case .rebooted(let v)? = payloadVariant {return v} return false } - set {_uniqueStorage()._payloadVariant = .rebooted(newValue)} + set {payloadVariant = .rebooted(newValue)} } /// /// Include module config var moduleConfig: ModuleConfig { get { - if case .moduleConfig(let v)? = _storage._payloadVariant {return v} + if case .moduleConfig(let v)? = payloadVariant {return v} return ModuleConfig() } - set {_uniqueStorage()._payloadVariant = .moduleConfig(newValue)} + set {payloadVariant = .moduleConfig(newValue)} } /// /// One packet is sent for each channel var channel: Channel { get { - if case .channel(let v)? = _storage._payloadVariant {return v} + if case .channel(let v)? = payloadVariant {return v} return Channel() } - set {_uniqueStorage()._payloadVariant = .channel(newValue)} + set {payloadVariant = .channel(newValue)} } /// /// Queue status info var queueStatus: QueueStatus { get { - if case .queueStatus(let v)? = _storage._payloadVariant {return v} + if case .queueStatus(let v)? = payloadVariant {return v} return QueueStatus() } - set {_uniqueStorage()._payloadVariant = .queueStatus(newValue)} + set {payloadVariant = .queueStatus(newValue)} } /// /// File Transfer Chunk var xmodemPacket: XModem { get { - if case .xmodemPacket(let v)? = _storage._payloadVariant {return v} + if case .xmodemPacket(let v)? = payloadVariant {return v} return XModem() } - set {_uniqueStorage()._payloadVariant = .xmodemPacket(newValue)} + set {payloadVariant = .xmodemPacket(newValue)} } /// /// Device metadata message var metadata: DeviceMetadata { get { - if case .metadata(let v)? = _storage._payloadVariant {return v} + if case .metadata(let v)? = payloadVariant {return v} return DeviceMetadata() } - set {_uniqueStorage()._payloadVariant = .metadata(newValue)} + set {payloadVariant = .metadata(newValue)} } /// /// MQTT Client Proxy Message (device sending to client / phone for publishing to MQTT) var mqttClientProxyMessage: MqttClientProxyMessage { get { - if case .mqttClientProxyMessage(let v)? = _storage._payloadVariant {return v} + if case .mqttClientProxyMessage(let v)? = payloadVariant {return v} return MqttClientProxyMessage() } - set {_uniqueStorage()._payloadVariant = .mqttClientProxyMessage(newValue)} + set {payloadVariant = .mqttClientProxyMessage(newValue)} } var unknownFields = SwiftProtobuf.UnknownStorage() @@ -2243,8 +2172,6 @@ struct FromRadio { } init() {} - - fileprivate var _storage = _StorageClass.defaultInstance } /// @@ -2414,6 +2341,10 @@ struct NeighborInfo { /// Field to pass neighbor info for the next sending cycle var lastSentByID: UInt32 = 0 + /// + /// Broadcast interval of the represented node (in seconds) + var nodeBroadcastIntervalSecs: UInt32 = 0 + /// /// The list of out edges from this node var neighbors: [Neighbor] = [] @@ -2438,6 +2369,16 @@ struct Neighbor { /// SNR of last heard message var snr: Float = 0 + /// + /// Reception time (in secs since 1970) of last message that was last sent by this ID. + /// Note: this is for local storage only and will not be sent out over the mesh. + var lastRxTime: UInt32 = 0 + + /// + /// Broadcast interval of this neighbor (in seconds). + /// Note: this is for local storage only and will not be sent out over the mesh. + var nodeBroadcastIntervalSecs: UInt32 = 0 + var unknownFields = SwiftProtobuf.UnknownStorage() init() {} @@ -2577,6 +2518,7 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding { 49: .same(proto: "HELTEC_WIRELESS_PAPER"), 50: .same(proto: "T_DECK"), 51: .same(proto: "T_WATCH_S3"), + 52: .same(proto: "PICOMPUTER_S3"), 255: .same(proto: "PRIVATE_HW"), ] } @@ -3551,21 +3493,8 @@ extension MyNodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio static let protoMessageName: String = _protobuf_package + ".MyNodeInfo" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .standard(proto: "my_node_num"), - 2: .standard(proto: "has_gps"), - 3: .standard(proto: "max_channels"), - 4: .standard(proto: "firmware_version"), - 5: .standard(proto: "error_code"), - 6: .standard(proto: "error_address"), - 7: .standard(proto: "error_count"), 8: .standard(proto: "reboot_count"), - 9: .same(proto: "bitrate"), - 10: .standard(proto: "message_timeout_msec"), 11: .standard(proto: "min_app_version"), - 12: .standard(proto: "air_period_tx"), - 13: .standard(proto: "air_period_rx"), - 14: .standard(proto: "has_wifi"), - 15: .standard(proto: "channel_utilization"), - 16: .standard(proto: "air_util_tx"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -3575,21 +3504,8 @@ extension MyNodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularUInt32Field(value: &self.myNodeNum) }() - case 2: try { try decoder.decodeSingularBoolField(value: &self.hasGps_p) }() - case 3: try { try decoder.decodeSingularUInt32Field(value: &self.maxChannels) }() - case 4: try { try decoder.decodeSingularStringField(value: &self.firmwareVersion) }() - case 5: try { try decoder.decodeSingularEnumField(value: &self.errorCode) }() - case 6: try { try decoder.decodeSingularUInt32Field(value: &self.errorAddress) }() - case 7: try { try decoder.decodeSingularUInt32Field(value: &self.errorCount) }() case 8: try { try decoder.decodeSingularUInt32Field(value: &self.rebootCount) }() - case 9: try { try decoder.decodeSingularFloatField(value: &self.bitrate) }() - case 10: try { try decoder.decodeSingularUInt32Field(value: &self.messageTimeoutMsec) }() case 11: try { try decoder.decodeSingularUInt32Field(value: &self.minAppVersion) }() - case 12: try { try decoder.decodeRepeatedUInt32Field(value: &self.airPeriodTx) }() - case 13: try { try decoder.decodeRepeatedUInt32Field(value: &self.airPeriodRx) }() - case 14: try { try decoder.decodeSingularBoolField(value: &self.hasWifi_p) }() - case 15: try { try decoder.decodeSingularFloatField(value: &self.channelUtilization) }() - case 16: try { try decoder.decodeSingularFloatField(value: &self.airUtilTx) }() default: break } } @@ -3599,71 +3515,19 @@ extension MyNodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio if self.myNodeNum != 0 { try visitor.visitSingularUInt32Field(value: self.myNodeNum, fieldNumber: 1) } - if self.hasGps_p != false { - try visitor.visitSingularBoolField(value: self.hasGps_p, fieldNumber: 2) - } - if self.maxChannels != 0 { - try visitor.visitSingularUInt32Field(value: self.maxChannels, fieldNumber: 3) - } - if !self.firmwareVersion.isEmpty { - try visitor.visitSingularStringField(value: self.firmwareVersion, fieldNumber: 4) - } - if self.errorCode != .none { - try visitor.visitSingularEnumField(value: self.errorCode, fieldNumber: 5) - } - if self.errorAddress != 0 { - try visitor.visitSingularUInt32Field(value: self.errorAddress, fieldNumber: 6) - } - if self.errorCount != 0 { - try visitor.visitSingularUInt32Field(value: self.errorCount, fieldNumber: 7) - } if self.rebootCount != 0 { try visitor.visitSingularUInt32Field(value: self.rebootCount, fieldNumber: 8) } - if self.bitrate != 0 { - try visitor.visitSingularFloatField(value: self.bitrate, fieldNumber: 9) - } - if self.messageTimeoutMsec != 0 { - try visitor.visitSingularUInt32Field(value: self.messageTimeoutMsec, fieldNumber: 10) - } if self.minAppVersion != 0 { try visitor.visitSingularUInt32Field(value: self.minAppVersion, fieldNumber: 11) } - if !self.airPeriodTx.isEmpty { - try visitor.visitPackedUInt32Field(value: self.airPeriodTx, fieldNumber: 12) - } - if !self.airPeriodRx.isEmpty { - try visitor.visitPackedUInt32Field(value: self.airPeriodRx, fieldNumber: 13) - } - if self.hasWifi_p != false { - try visitor.visitSingularBoolField(value: self.hasWifi_p, fieldNumber: 14) - } - if self.channelUtilization != 0 { - try visitor.visitSingularFloatField(value: self.channelUtilization, fieldNumber: 15) - } - if self.airUtilTx != 0 { - try visitor.visitSingularFloatField(value: self.airUtilTx, fieldNumber: 16) - } try unknownFields.traverse(visitor: &visitor) } static func ==(lhs: MyNodeInfo, rhs: MyNodeInfo) -> Bool { if lhs.myNodeNum != rhs.myNodeNum {return false} - if lhs.hasGps_p != rhs.hasGps_p {return false} - if lhs.maxChannels != rhs.maxChannels {return false} - if lhs.firmwareVersion != rhs.firmwareVersion {return false} - if lhs.errorCode != rhs.errorCode {return false} - if lhs.errorAddress != rhs.errorAddress {return false} - if lhs.errorCount != rhs.errorCount {return false} if lhs.rebootCount != rhs.rebootCount {return false} - if lhs.bitrate != rhs.bitrate {return false} - if lhs.messageTimeoutMsec != rhs.messageTimeoutMsec {return false} if lhs.minAppVersion != rhs.minAppVersion {return false} - if lhs.airPeriodTx != rhs.airPeriodTx {return false} - if lhs.airPeriodRx != rhs.airPeriodRx {return false} - if lhs.hasWifi_p != rhs.hasWifi_p {return false} - if lhs.channelUtilization != rhs.channelUtilization {return false} - if lhs.airUtilTx != rhs.airUtilTx {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -3800,280 +3664,246 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation 14: .same(proto: "mqttClientProxyMessage"), ] - fileprivate class _StorageClass { - var _id: UInt32 = 0 - var _payloadVariant: FromRadio.OneOf_PayloadVariant? - - static let defaultInstance = _StorageClass() - - private init() {} - - init(copying source: _StorageClass) { - _id = source._id - _payloadVariant = source._payloadVariant - } - } - - fileprivate mutating func _uniqueStorage() -> _StorageClass { - if !isKnownUniquelyReferenced(&_storage) { - _storage = _StorageClass(copying: _storage) - } - return _storage - } - mutating func decodeMessage(decoder: inout D) throws { - _ = _uniqueStorage() - try withExtendedLifetime(_storage) { (_storage: _StorageClass) in - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularUInt32Field(value: &_storage._id) }() - case 2: try { - var v: MeshPacket? - var hadOneofValue = false - if let current = _storage._payloadVariant { - hadOneofValue = true - if case .packet(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - _storage._payloadVariant = .packet(v) - } - }() - case 3: try { - var v: MyNodeInfo? - var hadOneofValue = false - if let current = _storage._payloadVariant { - hadOneofValue = true - if case .myInfo(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - _storage._payloadVariant = .myInfo(v) - } - }() - case 4: try { - var v: NodeInfo? - var hadOneofValue = false - if let current = _storage._payloadVariant { - hadOneofValue = true - if case .nodeInfo(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - _storage._payloadVariant = .nodeInfo(v) - } - }() - case 5: try { - var v: Config? - var hadOneofValue = false - if let current = _storage._payloadVariant { - hadOneofValue = true - if case .config(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - _storage._payloadVariant = .config(v) - } - }() - case 6: try { - var v: LogRecord? - var hadOneofValue = false - if let current = _storage._payloadVariant { - hadOneofValue = true - if case .logRecord(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - _storage._payloadVariant = .logRecord(v) - } - }() - case 7: try { - var v: UInt32? - try decoder.decodeSingularUInt32Field(value: &v) - if let v = v { - if _storage._payloadVariant != nil {try decoder.handleConflictingOneOf()} - _storage._payloadVariant = .configCompleteID(v) - } - }() - case 8: try { - var v: Bool? - try decoder.decodeSingularBoolField(value: &v) - if let v = v { - if _storage._payloadVariant != nil {try decoder.handleConflictingOneOf()} - _storage._payloadVariant = .rebooted(v) - } - }() - case 9: try { - var v: ModuleConfig? - var hadOneofValue = false - if let current = _storage._payloadVariant { - hadOneofValue = true - if case .moduleConfig(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - _storage._payloadVariant = .moduleConfig(v) - } - }() - case 10: try { - var v: Channel? - var hadOneofValue = false - if let current = _storage._payloadVariant { - hadOneofValue = true - if case .channel(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - _storage._payloadVariant = .channel(v) - } - }() - case 11: try { - var v: QueueStatus? - var hadOneofValue = false - if let current = _storage._payloadVariant { - hadOneofValue = true - if case .queueStatus(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - _storage._payloadVariant = .queueStatus(v) - } - }() - case 12: try { - var v: XModem? - var hadOneofValue = false - if let current = _storage._payloadVariant { - hadOneofValue = true - if case .xmodemPacket(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - _storage._payloadVariant = .xmodemPacket(v) - } - }() - case 13: try { - var v: DeviceMetadata? - var hadOneofValue = false - if let current = _storage._payloadVariant { - hadOneofValue = true - if case .metadata(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - _storage._payloadVariant = .metadata(v) - } - }() - case 14: try { - var v: MqttClientProxyMessage? - var hadOneofValue = false - if let current = _storage._payloadVariant { - hadOneofValue = true - if case .mqttClientProxyMessage(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - _storage._payloadVariant = .mqttClientProxyMessage(v) - } - }() - default: break + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.id) }() + case 2: try { + var v: MeshPacket? + var hadOneofValue = false + if let current = self.payloadVariant { + hadOneofValue = true + if case .packet(let m) = current {v = m} } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.payloadVariant = .packet(v) + } + }() + case 3: try { + var v: MyNodeInfo? + var hadOneofValue = false + if let current = self.payloadVariant { + hadOneofValue = true + if case .myInfo(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.payloadVariant = .myInfo(v) + } + }() + case 4: try { + var v: NodeInfo? + var hadOneofValue = false + if let current = self.payloadVariant { + hadOneofValue = true + if case .nodeInfo(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.payloadVariant = .nodeInfo(v) + } + }() + case 5: try { + var v: Config? + var hadOneofValue = false + if let current = self.payloadVariant { + hadOneofValue = true + if case .config(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.payloadVariant = .config(v) + } + }() + case 6: try { + var v: LogRecord? + var hadOneofValue = false + if let current = self.payloadVariant { + hadOneofValue = true + if case .logRecord(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.payloadVariant = .logRecord(v) + } + }() + case 7: try { + var v: UInt32? + try decoder.decodeSingularUInt32Field(value: &v) + if let v = v { + if self.payloadVariant != nil {try decoder.handleConflictingOneOf()} + self.payloadVariant = .configCompleteID(v) + } + }() + case 8: try { + var v: Bool? + try decoder.decodeSingularBoolField(value: &v) + if let v = v { + if self.payloadVariant != nil {try decoder.handleConflictingOneOf()} + self.payloadVariant = .rebooted(v) + } + }() + case 9: try { + var v: ModuleConfig? + var hadOneofValue = false + if let current = self.payloadVariant { + hadOneofValue = true + if case .moduleConfig(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.payloadVariant = .moduleConfig(v) + } + }() + case 10: try { + var v: Channel? + var hadOneofValue = false + if let current = self.payloadVariant { + hadOneofValue = true + if case .channel(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.payloadVariant = .channel(v) + } + }() + case 11: try { + var v: QueueStatus? + var hadOneofValue = false + if let current = self.payloadVariant { + hadOneofValue = true + if case .queueStatus(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.payloadVariant = .queueStatus(v) + } + }() + case 12: try { + var v: XModem? + var hadOneofValue = false + if let current = self.payloadVariant { + hadOneofValue = true + if case .xmodemPacket(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.payloadVariant = .xmodemPacket(v) + } + }() + case 13: try { + var v: DeviceMetadata? + var hadOneofValue = false + if let current = self.payloadVariant { + hadOneofValue = true + if case .metadata(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.payloadVariant = .metadata(v) + } + }() + case 14: try { + var v: MqttClientProxyMessage? + var hadOneofValue = false + if let current = self.payloadVariant { + hadOneofValue = true + if case .mqttClientProxyMessage(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.payloadVariant = .mqttClientProxyMessage(v) + } + }() + default: break } } } func traverse(visitor: inout V) throws { - try withExtendedLifetime(_storage) { (_storage: _StorageClass) in - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - if _storage._id != 0 { - try visitor.visitSingularUInt32Field(value: _storage._id, fieldNumber: 1) - } - switch _storage._payloadVariant { - case .packet?: try { - guard case .packet(let v)? = _storage._payloadVariant else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - }() - case .myInfo?: try { - guard case .myInfo(let v)? = _storage._payloadVariant else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - }() - case .nodeInfo?: try { - guard case .nodeInfo(let v)? = _storage._payloadVariant else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 4) - }() - case .config?: try { - guard case .config(let v)? = _storage._payloadVariant else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 5) - }() - case .logRecord?: try { - guard case .logRecord(let v)? = _storage._payloadVariant else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 6) - }() - case .configCompleteID?: try { - guard case .configCompleteID(let v)? = _storage._payloadVariant else { preconditionFailure() } - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 7) - }() - case .rebooted?: try { - guard case .rebooted(let v)? = _storage._payloadVariant else { preconditionFailure() } - try visitor.visitSingularBoolField(value: v, fieldNumber: 8) - }() - case .moduleConfig?: try { - guard case .moduleConfig(let v)? = _storage._payloadVariant else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 9) - }() - case .channel?: try { - guard case .channel(let v)? = _storage._payloadVariant else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 10) - }() - case .queueStatus?: try { - guard case .queueStatus(let v)? = _storage._payloadVariant else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 11) - }() - case .xmodemPacket?: try { - guard case .xmodemPacket(let v)? = _storage._payloadVariant else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 12) - }() - case .metadata?: try { - guard case .metadata(let v)? = _storage._payloadVariant else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 13) - }() - case .mqttClientProxyMessage?: try { - guard case .mqttClientProxyMessage(let v)? = _storage._payloadVariant else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 14) - }() - case nil: break - } + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.id != 0 { + try visitor.visitSingularUInt32Field(value: self.id, fieldNumber: 1) + } + switch self.payloadVariant { + case .packet?: try { + guard case .packet(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + }() + case .myInfo?: try { + guard case .myInfo(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + }() + case .nodeInfo?: try { + guard case .nodeInfo(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + }() + case .config?: try { + guard case .config(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 5) + }() + case .logRecord?: try { + guard case .logRecord(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 6) + }() + case .configCompleteID?: try { + guard case .configCompleteID(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 7) + }() + case .rebooted?: try { + guard case .rebooted(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularBoolField(value: v, fieldNumber: 8) + }() + case .moduleConfig?: try { + guard case .moduleConfig(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 9) + }() + case .channel?: try { + guard case .channel(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 10) + }() + case .queueStatus?: try { + guard case .queueStatus(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 11) + }() + case .xmodemPacket?: try { + guard case .xmodemPacket(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 12) + }() + case .metadata?: try { + guard case .metadata(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 13) + }() + case .mqttClientProxyMessage?: try { + guard case .mqttClientProxyMessage(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 14) + }() + case nil: break } try unknownFields.traverse(visitor: &visitor) } static func ==(lhs: FromRadio, rhs: FromRadio) -> Bool { - if lhs._storage !== rhs._storage { - let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in - let _storage = _args.0 - let rhs_storage = _args.1 - if _storage._id != rhs_storage._id {return false} - if _storage._payloadVariant != rhs_storage._payloadVariant {return false} - return true - } - if !storagesAreEqual {return false} - } + if lhs.id != rhs.id {return false} + if lhs.payloadVariant != rhs.payloadVariant {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -4236,7 +4066,8 @@ extension NeighborInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .standard(proto: "node_id"), 2: .standard(proto: "last_sent_by_id"), - 3: .same(proto: "neighbors"), + 3: .standard(proto: "node_broadcast_interval_secs"), + 4: .same(proto: "neighbors"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -4247,7 +4078,8 @@ extension NeighborInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat switch fieldNumber { case 1: try { try decoder.decodeSingularUInt32Field(value: &self.nodeID) }() case 2: try { try decoder.decodeSingularUInt32Field(value: &self.lastSentByID) }() - case 3: try { try decoder.decodeRepeatedMessageField(value: &self.neighbors) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self.nodeBroadcastIntervalSecs) }() + case 4: try { try decoder.decodeRepeatedMessageField(value: &self.neighbors) }() default: break } } @@ -4260,8 +4092,11 @@ extension NeighborInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat if self.lastSentByID != 0 { try visitor.visitSingularUInt32Field(value: self.lastSentByID, fieldNumber: 2) } + if self.nodeBroadcastIntervalSecs != 0 { + try visitor.visitSingularUInt32Field(value: self.nodeBroadcastIntervalSecs, fieldNumber: 3) + } if !self.neighbors.isEmpty { - try visitor.visitRepeatedMessageField(value: self.neighbors, fieldNumber: 3) + try visitor.visitRepeatedMessageField(value: self.neighbors, fieldNumber: 4) } try unknownFields.traverse(visitor: &visitor) } @@ -4269,6 +4104,7 @@ extension NeighborInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat static func ==(lhs: NeighborInfo, rhs: NeighborInfo) -> Bool { if lhs.nodeID != rhs.nodeID {return false} if lhs.lastSentByID != rhs.lastSentByID {return false} + if lhs.nodeBroadcastIntervalSecs != rhs.nodeBroadcastIntervalSecs {return false} if lhs.neighbors != rhs.neighbors {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true @@ -4280,6 +4116,8 @@ extension Neighbor: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .standard(proto: "node_id"), 2: .same(proto: "snr"), + 3: .standard(proto: "last_rx_time"), + 4: .standard(proto: "node_broadcast_interval_secs"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -4290,6 +4128,8 @@ extension Neighbor: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB switch fieldNumber { case 1: try { try decoder.decodeSingularUInt32Field(value: &self.nodeID) }() case 2: try { try decoder.decodeSingularFloatField(value: &self.snr) }() + case 3: try { try decoder.decodeSingularFixed32Field(value: &self.lastRxTime) }() + case 4: try { try decoder.decodeSingularUInt32Field(value: &self.nodeBroadcastIntervalSecs) }() default: break } } @@ -4302,12 +4142,20 @@ extension Neighbor: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB if self.snr != 0 { try visitor.visitSingularFloatField(value: self.snr, fieldNumber: 2) } + if self.lastRxTime != 0 { + try visitor.visitSingularFixed32Field(value: self.lastRxTime, fieldNumber: 3) + } + if self.nodeBroadcastIntervalSecs != 0 { + try visitor.visitSingularUInt32Field(value: self.nodeBroadcastIntervalSecs, fieldNumber: 4) + } try unknownFields.traverse(visitor: &visitor) } static func ==(lhs: Neighbor, rhs: Neighbor) -> Bool { if lhs.nodeID != rhs.nodeID {return false} if lhs.snr != rhs.snr {return false} + if lhs.lastRxTime != rhs.lastRxTime {return false} + if lhs.nodeBroadcastIntervalSecs != rhs.nodeBroadcastIntervalSecs {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/Meshtastic/Protobufs/meshtastic/portnums.pb.swift b/Meshtastic/Protobufs/meshtastic/portnums.pb.swift index 29f7b471..5cdd6cd0 100644 --- a/Meshtastic/Protobufs/meshtastic/portnums.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/portnums.pb.swift @@ -40,60 +40,77 @@ enum PortNum: SwiftProtobuf.Enum { /// Deprecated: do not use in new code (formerly called OPAQUE) /// A message sent from a device outside of the mesh, in a form the mesh does not understand /// NOTE: This must be 0, because it is documented in IMeshService.aidl to be so + /// ENCODING: binary undefined case unknownApp // = 0 /// /// A simple UTF-8 text message, which even the little micros in the mesh /// can understand and show on their screen eventually in some circumstances /// even signal might send messages in this form (see below) + /// ENCODING: UTF-8 Plaintext (?) case textMessageApp // = 1 /// /// Reserved for built-in GPIO/example app. /// See remote_hardware.proto/HardwareMessage for details on the message sent/received to this port number + /// ENCODING: Protobuf case remoteHardwareApp // = 2 /// /// The built-in position messaging app. /// Payload is a [Position](/docs/developers/protobufs/api#position) message + /// ENCODING: Protobuf case positionApp // = 3 /// /// The built-in user info app. /// Payload is a [User](/docs/developers/protobufs/api#user) message + /// ENCODING: Protobuf case nodeinfoApp // = 4 /// /// Protocol control packets for mesh protocol use. /// Payload is a [Routing](/docs/developers/protobufs/api#routing) message + /// ENCODING: Protobuf case routingApp // = 5 /// /// Admin control packets. /// Payload is a [AdminMessage](/docs/developers/protobufs/api#adminmessage) message + /// ENCODING: Protobuf case adminApp // = 6 /// /// Compressed TEXT_MESSAGE payloads. + /// ENCODING: UTF-8 Plaintext (?) with Unishox2 Compression + /// NOTE: The Device Firmware converts a TEXT_MESSAGE_APP to TEXT_MESSAGE_COMPRESSED_APP if the compressed + /// payload is shorter. There's no need for app developers to do this themselves. Also the firmware will decompress + /// any incoming TEXT_MESSAGE_COMPRESSED_APP payload and convert to TEXT_MESSAGE_APP. case textMessageCompressedApp // = 7 /// /// Waypoint payloads. /// Payload is a [Waypoint](/docs/developers/protobufs/api#waypoint) message + /// ENCODING: Protobuf case waypointApp // = 8 /// /// Audio Payloads. /// Encapsulated codec2 packets. On 2.4 GHZ Bandwidths only for now + /// ENCODING: codec2 audio frames + /// NOTE: audio frames contain a 3 byte header (0xc0 0xde 0xc2) and a one byte marker for the decompressed bitrate. + /// This marker comes from the 'moduleConfig.audio.bitrate' enum minus one. case audioApp // = 9 /// /// Provides a 'ping' service that replies to any packet it receives. /// Also serves as a small example module. + /// ENCODING: ASCII Plaintext case replyApp // = 32 /// /// Used for the python IP tunnel feature + /// ENCODING: IP Packet. Handled by the python API, firmware ignores this one and pases on. case ipTunnelApp // = 33 /// @@ -102,26 +119,31 @@ enum PortNum: SwiftProtobuf.Enum { /// network is forwarded to the RX pin while sending a packet to TX will go out to the Mesh network. /// Maximum packet size of 240 bytes. /// Module is disabled by default can be turned on by setting SERIAL_MODULE_ENABLED = 1 in SerialPlugh.cpp. + /// ENCODING: binary undefined case serialApp // = 64 /// /// STORE_FORWARD_APP (Work in Progress) /// Maintained by Jm Casler (MC Hamster) : jm@casler.org + /// ENCODING: Protobuf case storeForwardApp // = 65 /// /// Optional port for messages for the range test module. + /// ENCODING: ASCII Plaintext case rangeTestApp // = 66 /// /// Provides a format to send and receive telemetry data from the Meshtastic network. /// Maintained by Charles Crossan (crossan007) : crossan007@gmail.com + /// ENCODING: Protobuf case telemetryApp // = 67 /// /// Experimental tools for estimating node position without a GPS /// Maintained by Github user a-f-G-U-C (a Meshtastic contributor) /// Project files at https://github.com/a-f-G-U-C/Meshtastic-ZPS + /// ENCODING: arrays of int64 fields case zpsApp // = 68 /// @@ -129,15 +151,18 @@ enum PortNum: SwiftProtobuf.Enum { /// as if they did using their LoRa chip. /// Maintained by GitHub user GUVWAF. /// Project files at https://github.com/GUVWAF/Meshtasticator + /// ENCODING: Protobuf (?) case simulatorApp // = 69 /// /// Provides a traceroute functionality to show the route a packet towards /// a certain destination would take on the mesh. + /// ENCODING: Protobuf case tracerouteApp // = 70 /// /// Aggregates edge info for the network by sending out a list of each node's neighbors + /// ENCODING: Protobuf case neighborinfoApp // = 71 /// @@ -148,6 +173,7 @@ enum PortNum: SwiftProtobuf.Enum { /// /// ATAK Forwarder Module https://github.com/paulmandal/atak-forwarder + /// ENCODING: libcotshrink case atakForwarder // = 257 /// diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index fd11b1c4..c3f3f55f 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -50,7 +50,7 @@ struct Connect: View { if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == .connected { HStack { VStack(alignment: .center) { - CircleText(text: node?.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node?.num ?? 0))), circleSize: 80, fontSize: (node?.user?.shortName ?? "???").isEmoji() ? 52 : 30, textColor: UIColor(hex: UInt32(node?.num ?? 0)).isLight() ? .black : .white ) + CircleText(text: node?.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node?.num ?? 0))), circleSize: 90, fontSize: (node?.user?.shortName ?? "???").isEmoji() ? 52 : (node?.user?.shortName?.count ?? 0 == 4 ? 26 : 36), textColor: UIColor(hex: UInt32(node?.num ?? 0)).isLight() ? .black : .white ) } .padding(.trailing) VStack(alignment: .leading) { diff --git a/Meshtastic/Views/Helpers/CircleText.swift b/Meshtastic/Views/Helpers/CircleText.swift index 30d40406..2f8ebb05 100644 --- a/Meshtastic/Views/Helpers/CircleText.swift +++ b/Meshtastic/Views/Helpers/CircleText.swift @@ -30,7 +30,7 @@ struct CircleText: View { struct CircleText_Previews: PreviewProvider { static var previews: some View { - CircleText(text: "RDDN", color: Color.accentColor) + CircleText(text: "MOMO", color: Color.accentColor) .previewLayout(.fixed(width: 300, height: 100)) } } diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index 86e3bd33..60216d14 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -47,8 +47,7 @@ struct DeviceMetricsLog: View { .accessibilityLabel("Line Series") .accessibilityValue("X: \(point.time!), Y: \(point.batteryLevel)") .foregroundStyle(batteryChartColor) - .interpolationMethod(.cardinal) - //.interpolationMethod(.catmullRom(alpha: 1.0)) + .interpolationMethod(.catmullRom(alpha: 1.0)) Plot { PointMark( @@ -181,7 +180,7 @@ struct DeviceMetricsLog: View { .buttonBorderShape(.capsule) .controlSize(.large) .padding(.bottom) - .padding(.trailing) + .padding(.leading) .confirmationDialog( "are.you.sure", isPresented: $isPresentingClearLogConfirm, @@ -195,6 +194,7 @@ struct DeviceMetricsLog: View { } } } + Button { exportString = telemetryToCsvFile(telemetry: deviceMetrics, metricsType: 0) isExporting = true diff --git a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift index 26e24e21..dc490a84 100644 --- a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift +++ b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift @@ -169,7 +169,7 @@ struct EnvironmentMetricsLog: View { .buttonBorderShape(.capsule) .controlSize(.large) .padding(.bottom) - .padding(.trailing) + .padding(.leading) .confirmationDialog( "are.you.sure", isPresented: $isPresentingClearLogConfirm, @@ -191,7 +191,7 @@ struct EnvironmentMetricsLog: View { .buttonBorderShape(.capsule) .controlSize(.large) .padding(.bottom) - .padding(.leading) + .padding(.trailing) } .navigationTitle("Environment Metrics Log") .navigationBarTitleDisplayMode(.inline) diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 759cd6ca..d9a497ef 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -38,7 +38,7 @@ struct NodeList: View { LazyVStack(alignment: .leading) { HStack { VStack(alignment: .leading) { - CircleText(text: node.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65, fontSize: (node.user?.shortName ?? "???").isEmoji() ? 44 : 22, brightness: 0.0, textColor: UIColor(hex: UInt32(node.num)).isLight() ? .black : .white) + CircleText(text: node.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65, fontSize: (node.user?.shortName ?? "???").isEmoji() ? 44 : (node.user?.shortName?.count ?? 0 == 4 ? 19 : 26), brightness: 0.0, textColor: UIColor(hex: UInt32(node.num)).isLight() ? .black : .white) .padding(.trailing, 5) let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")) if deviceMetrics?.count ?? 0 >= 1 { diff --git a/Meshtastic/Views/Nodes/PositionLog.swift b/Meshtastic/Views/Nodes/PositionLog.swift index 87488d6b..95df3f75 100644 --- a/Meshtastic/Views/Nodes/PositionLog.swift +++ b/Meshtastic/Views/Nodes/PositionLog.swift @@ -127,7 +127,7 @@ struct PositionLog: View { .buttonBorderShape(.capsule) .controlSize(.large) .padding(.bottom) - .padding(.trailing) + .padding(.leading) .confirmationDialog( "are.you.sure", isPresented: $isPresentingClearLogConfirm, @@ -154,7 +154,7 @@ struct PositionLog: View { .buttonBorderShape(.capsule) .controlSize(.large) .padding(.bottom) - .padding(.leading) + .padding(.trailing) } .fileExporter( isPresented: $isExporting, diff --git a/Meshtastic/Views/Settings/MeshLog.swift b/Meshtastic/Views/Settings/MeshLog.swift index a5f27761..4f0072ca 100644 --- a/Meshtastic/Views/Settings/MeshLog.swift +++ b/Meshtastic/Views/Settings/MeshLog.swift @@ -67,23 +67,24 @@ struct MeshLog: View { print(error) } } label: { - Label("Clear Log", systemImage: "trash.fill") + Label("Clear", systemImage: "trash.fill") } .buttonStyle(.bordered) .buttonBorderShape(.capsule) .controlSize(.large) - .padding() - Spacer() + .padding(.bottom) + .padding(.leading) Button { isExporting = true } label: { - Label("Save Log", systemImage: "square.and.arrow.down") + Label("Save", systemImage: "square.and.arrow.down") } .buttonStyle(.bordered) .buttonBorderShape(.capsule) .controlSize(.large) - .padding() + .padding(.bottom) + .padding(.trailing) Spacer() } .padding(.bottom, 10) From 9dc36572510f70fc993740c59862bd19d216073b Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 11 Aug 2023 08:49:30 -0700 Subject: [PATCH 02/40] Bump version --- Meshtastic.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index b53e62c0..c8cf64cf 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1481,7 +1481,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.0; + MARKETING_VERSION = 2.2.1; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -1512,7 +1512,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.0; + MARKETING_VERSION = 2.2.1; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; From ddad4d49ceefa958d14850c1a5456481d304b84f Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 14 Aug 2023 14:41:26 -0700 Subject: [PATCH 03/40] Remove ESP32 range test restriction Clean up buttons Remove min app version field --- Meshtastic.xcodeproj/project.pbxproj | 8 ++++---- Meshtastic/Helpers/MeshPackets.swift | 2 -- Meshtastic/Views/Helpers/ConnectedDevice.swift | 11 ++++++++++- Meshtastic/Views/Settings/Config/DeviceConfig.swift | 4 ++-- Meshtastic/Views/Settings/Config/LoRaConfig.swift | 2 +- .../Views/Settings/Config/Module/MQTTConfig.swift | 4 ++-- .../Settings/Config/Module/RangeTestConfig.swift | 4 ++-- 7 files changed, 21 insertions(+), 14 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index c8cf64cf..83833136 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1323,12 +1323,12 @@ INFOPLIST_FILE = Meshtastic/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Meshtastic; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.1; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.2.0; + MARKETING_VERSION = 2.2.1; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1357,12 +1357,12 @@ INFOPLIST_FILE = Meshtastic/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Meshtastic; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.1; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.2.0; + MARKETING_VERSION = 2.2.1; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index d24e5bb0..382a4619 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -90,7 +90,6 @@ func myInfoPacket (myInfo: MyNodeInfo, peripheralId: String, context: NSManagedO myInfoEntity.peripheralId = peripheralId myInfoEntity.myNodeNum = Int64(myInfo.myNodeNum) myInfoEntity.rebootCount = Int32(myInfo.rebootCount) - myInfoEntity.minAppVersion = Int32(bitPattern: myInfo.minAppVersion) do { try context.save() print("💾 Saved a new myInfo for node number: \(String(myInfo.myNodeNum))") @@ -105,7 +104,6 @@ func myInfoPacket (myInfo: MyNodeInfo, peripheralId: String, context: NSManagedO fetchedMyInfo[0].peripheralId = peripheralId fetchedMyInfo[0].myNodeNum = Int64(myInfo.myNodeNum) fetchedMyInfo[0].rebootCount = Int32(myInfo.rebootCount) - fetchedMyInfo[0].minAppVersion = Int32(bitPattern: myInfo.minAppVersion) do { try context.save() diff --git a/Meshtastic/Views/Helpers/ConnectedDevice.swift b/Meshtastic/Views/Helpers/ConnectedDevice.swift index f3373654..3e331e5e 100644 --- a/Meshtastic/Views/Helpers/ConnectedDevice.swift +++ b/Meshtastic/Views/Helpers/ConnectedDevice.swift @@ -13,7 +13,16 @@ struct ConnectedDevice: View { var body: some View { HStack { - + Image(systemName: "iphone.gen3.radiowaves.left.and.right.circle.fill") + .imageScale(.large) + .foregroundColor(.green) + .symbolRenderingMode(.hierarchical) + + Image(systemName: "xmark.circle.fill") + .imageScale(.large) + .foregroundColor(.red) + .symbolRenderingMode(.hierarchical) + if bluetoothOn { if deviceConnected { Image(systemName: "antenna.radiowaves.left.and.right.circle.fill") diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index 55b6b423..6582a1a2 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -151,7 +151,7 @@ struct DeviceConfig: View { .buttonStyle(.bordered) .buttonBorderShape(.capsule) .controlSize(.large) - .padding() + .padding(.leading) .confirmationDialog( "are.you.sure", isPresented: $isPresentingNodeDBResetConfirm, @@ -174,7 +174,7 @@ struct DeviceConfig: View { .buttonStyle(.bordered) .buttonBorderShape(.capsule) .controlSize(.large) - .padding() + .padding(.trailing) .confirmationDialog( "All device and app data will be deleted. You will also need to forget your devices under Settings > Bluetooth.", isPresented: $isPresentingFactoryResetConfirm, diff --git a/Meshtastic/Views/Settings/Config/LoRaConfig.swift b/Meshtastic/Views/Settings/Config/LoRaConfig.swift index 7c6ff3b1..67c7ef57 100644 --- a/Meshtastic/Views/Settings/Config/LoRaConfig.swift +++ b/Meshtastic/Views/Settings/Config/LoRaConfig.swift @@ -144,7 +144,7 @@ struct LoRaConfig: View { } } .pickerStyle(DefaultPickerStyle()) - Text("Sets the maximum number of hops, default is 3. Increasing hops also increases air time utilization and should be used carefully.") + Text("Sets the maximum number of hops, default is 3. Increasing hops also increases congestion and should be used carefully.") .font(.caption) HStack { diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index e462f9a5..c7a9d562 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -65,6 +65,8 @@ struct MQTTConfig: View { Label("mqtt.clientproxy", systemImage: "iphone.radiowaves.left.and.right") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + Text("If both MQTT and the client proxy are enabled your device will utalize an available network connection to connect to the specified MQTT server.") + .font(.caption2) Toggle(isOn: $encryptionEnabled) { @@ -82,8 +84,6 @@ struct MQTTConfig: View { Label("JSON Enabled", systemImage: "ellipsis.curlybraces") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - Text("JSON mode is not reccomended it is incomplete and unstable.") - .font(.caption2) } Section(header: Text("Custom Server")) { HStack { diff --git a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift index 50b475d7..5d89dedb 100644 --- a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift @@ -72,13 +72,13 @@ struct RangeTestConfig: View { .font(.caption) } } - .disabled(self.bleManager.connectedPeripheral == nil || node?.rangeTestConfig == nil || !(node != nil && node?.metadata?.hasWifi ?? false)) + .disabled(self.bleManager.connectedPeripheral == nil || node?.rangeTestConfig == nil) Button { isPresentingSaveConfirm = true } label: { Label("save", systemImage: "square.and.arrow.down") } - .disabled(bleManager.connectedPeripheral == nil || !hasChanges || !(node?.metadata?.hasWifi ?? false)) + .disabled(bleManager.connectedPeripheral == nil || !hasChanges) .buttonStyle(.bordered) .buttonBorderShape(.capsule) .controlSize(.large) From 119043bc06c624d88b6a1d700d9f586423a998f0 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 15 Aug 2023 08:48:32 -0700 Subject: [PATCH 04/40] Clean up MQTT Connection indicator --- Meshtastic/Views/Bluetooth/Connect.swift | 2 +- .../Views/Helpers/ConnectedDevice.swift | 27 +++++++++---------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index c3f3f55f..84cbe2aa 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -251,7 +251,7 @@ struct Connect: View { .navigationTitle("bluetooth") .navigationBarItems(leading: MeshtasticLogo(), trailing: ZStack { - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????", mqttProxyConnected: bleManager.mqttManager.status == MqttClientProxyManager.ConnectionStatus.connected) }) } .sheet(isPresented: $invalidFirmwareVersion, onDismiss: didDismissSheet) { diff --git a/Meshtastic/Views/Helpers/ConnectedDevice.swift b/Meshtastic/Views/Helpers/ConnectedDevice.swift index 3e331e5e..8e908cc5 100644 --- a/Meshtastic/Views/Helpers/ConnectedDevice.swift +++ b/Meshtastic/Views/Helpers/ConnectedDevice.swift @@ -9,21 +9,21 @@ struct ConnectedDevice: View { var bluetoothOn: Bool var deviceConnected: Bool var name: String + var mqttProxyConnected: Bool = false var body: some View { HStack { - Image(systemName: "iphone.gen3.radiowaves.left.and.right.circle.fill") - .imageScale(.large) - .foregroundColor(.green) - .symbolRenderingMode(.hierarchical) - - Image(systemName: "xmark.circle.fill") - .imageScale(.large) - .foregroundColor(.red) - .symbolRenderingMode(.hierarchical) - - if bluetoothOn { + if bluetoothOn { + if deviceConnected && mqttProxyConnected { + + if mqttProxyConnected { + Image(systemName: "iphone.gen3.radiowaves.left.and.right.circle.fill") + .imageScale(.large) + .foregroundColor(.green) + .symbolRenderingMode(.hierarchical) + } + } if deviceConnected { Image(systemName: "antenna.radiowaves.left.and.right.circle.fill") .imageScale(.large) @@ -36,7 +36,6 @@ struct ConnectedDevice: View { .imageScale(.medium) .foregroundColor(.red) .symbolRenderingMode(.hierarchical) - } } else { Text("bluetooth.off").font(.subheadline).foregroundColor(.red) @@ -47,10 +46,10 @@ struct ConnectedDevice: View { struct ConnectedDevice_Previews: PreviewProvider { static var previews: some View { - ConnectedDevice(bluetoothOn: true, deviceConnected: false, name: "Yellow Beam") + ConnectedDevice(bluetoothOn: true, deviceConnected: true, name: "MEMO", mqttProxyConnected: true) .previewLayout(.fixed(width: 80, height: 70)) - ConnectedDevice(bluetoothOn: true, deviceConnected: false, name: "Yellow Beam") + ConnectedDevice(bluetoothOn: true, deviceConnected: false, name: "86D4", mqttProxyConnected: false) .previewLayout(.fixed(width: 80, height: 70)) } From 0231e110fbdb18c919ae833c9a1c98749785da83 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 15 Aug 2023 18:45:47 -0700 Subject: [PATCH 05/40] Update Mqtt proxy indicator on Bluetooth view to actually work. --- Meshtastic/Helpers/BLEManager.swift | 3 +++ Meshtastic/Views/Bluetooth/Connect.swift | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 0c758bb9..8d149579 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -13,12 +13,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate // MqttClientProxyManagerDelegate func onMqttConnected() { mqttManager.status = .connected + mqttProxyConnected = true print("📲 Mqtt Client Proxy onMqttConnected now subscribing to \(mqttManager.topic).") mqttManager.mqttClientProxy?.subscribe(mqttManager.topic) } func onMqttDisconnected() { mqttManager.status = .disconnected + mqttProxyConnected = false print("MQTT Disconnected") } @@ -66,6 +68,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate @Published var invalidVersion = false @Published var isSwitchedOn: Bool = false @Published var automaticallyReconnect: Bool = true + @Published var mqttProxyConnected: Bool = false public var minimumVersion = "2.0.0" public var connectedVersion: String public var isConnecting: Bool = false diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index 84cbe2aa..33725aca 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -251,7 +251,7 @@ struct Connect: View { .navigationTitle("bluetooth") .navigationBarItems(leading: MeshtasticLogo(), trailing: ZStack { - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????", mqttProxyConnected: bleManager.mqttManager.status == MqttClientProxyManager.ConnectionStatus.connected) + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????", mqttProxyConnected: bleManager.mqttProxyConnected) }) } .sheet(isPresented: $invalidFirmwareVersion, onDismiss: didDismissSheet) { From dc53ef743ed800b9e97d75b261ebc0a22197989c Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 15 Aug 2023 19:20:59 -0700 Subject: [PATCH 06/40] Delete unnecessary MQTT nonsense --- Meshtastic/Helpers/BLEManager.swift | 5 ++- .../Helpers/Mqtt/MqttClientProxyManager.swift | 42 ++----------------- 2 files changed, 7 insertions(+), 40 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 8d149579..4a510d41 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -12,14 +12,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate // MqttClientProxyManagerDelegate func onMqttConnected() { - mqttManager.status = .connected + //mqttManager.status = .connected mqttProxyConnected = true print("📲 Mqtt Client Proxy onMqttConnected now subscribing to \(mqttManager.topic).") mqttManager.mqttClientProxy?.subscribe(mqttManager.topic) } func onMqttDisconnected() { - mqttManager.status = .disconnected + // mqttManager.status = .disconnected mqttProxyConnected = false print("MQTT Disconnected") } @@ -47,6 +47,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } func onMqttError(message: String) { + mqttProxyConnected = false print("MQTT Error") } diff --git a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift index af22fcd1..e4a27531 100644 --- a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift +++ b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift @@ -16,31 +16,12 @@ protocol MqttClientProxyManagerDelegate: AnyObject { } class MqttClientProxyManager { - enum ConnectionStatus { - case connecting - case connected - case disconnecting - case disconnected - case error - case none - } - - enum MqttQos: Int { - case atMostOnce = 0 - case atLeastOnce = 1 - case exactlyOnce = 2 - } // Singleton Instance static let shared = MqttClientProxyManager() - private static let defaultKeepAliveInterval: Int32 = 60 - weak var delegate: MqttClientProxyManagerDelegate? - var status = ConnectionStatus.none - var mqttClientProxy: CocoaMQTT? - var topic = "msh/2/c" private init() { @@ -81,8 +62,6 @@ class MqttClientProxyManager { return } - status = .connecting - let clientId = "MeshtasticAppleMqttProxy-" + String(ProcessInfo().processIdentifier) mqttClientProxy = CocoaMQTT(clientID: clientId, host: host, port: UInt16(port)) @@ -103,17 +82,14 @@ class MqttClientProxyManager { let success = mqttClient.connect() if !success { delegate?.onMqttError(message: "Mqtt connect error") - status = .error } } else { delegate?.onMqttError(message: "Mqtt initialization error") - status = .error } } - func subscribe(topic: String, qos: MqttQos) { + func subscribe(topic: String, qos: CocoaMQTTQoS) { print("📲 MQTT Client Proxy subscribed to: " + topic) - let qos = CocoaMQTTQoS(rawValue :UInt8(qos.rawValue))! mqttClientProxy?.subscribe(topic, qos: qos) } @@ -122,21 +98,16 @@ class MqttClientProxyManager { print("📲 MQTT Client Proxy unsubscribe for: " + topic) } - func publish(message: String, topic: String, qos: MqttQos) { - let qos = CocoaMQTTQoS(rawValue :UInt8(qos.rawValue))! + func publish(message: String, topic: String, qos: CocoaMQTTQoS) { mqttClientProxy?.publish(topic, withString: message, qos: qos) print("📲 MQTT Client Proxy publish for: " + topic) } func disconnect() { - //MqttSettings.shared.isConnected = false if let client = mqttClientProxy { - status = .disconnecting client.disconnect() print("📲 MQTT Client Proxy Disconnected") - } else { - status = .disconnected } } } @@ -170,21 +141,16 @@ extension MqttClientProxyManager: CocoaMQTTDelegate { print(errorDescription) delegate?.onMqttError(message: errorDescription) - //self.disconnect() // Stop reconnecting - //mqttSettings.isConnected = false // Disable automatic connect on start + self.disconnect() } - - self.status = ack == .accept ? ConnectionStatus.connected : ConnectionStatus.error // Set AFTER sending onMqttError (so the delegate can detect that was an error while establishing connection) } func mqttDidDisconnect(_ mqtt: CocoaMQTT, withError err: Error?) { print("mqttDidDisconnect: \(err?.localizedDescription ?? "")") - if let error = err, status == .connecting { + if let error = err { delegate?.onMqttError(message: error.localizedDescription) } - - status = err == nil ? .disconnected : .error delegate?.onMqttDisconnected() } From a14026d7539e98662a5f07170a510167a3fac219 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 16 Aug 2023 18:28:55 -0700 Subject: [PATCH 07/40] Initial detection module config --- Meshtastic.xcodeproj/project.pbxproj | 8 +- Meshtastic/Helpers/BLEManager.swift | 81 ++--- .../Helpers/Mqtt/MqttClientProxyManager.swift | 5 - .../Meshtastic.xcdatamodeld/.xccurrentversion | 2 +- .../contents | 337 ++++++++++++++++++ .../Config/Module/DetectionSensorConfig.swift | 121 +++++++ Meshtastic/Views/Settings/Settings.swift | 12 + 7 files changed, 517 insertions(+), 49 deletions(-) create mode 100644 Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV16.xcdatamodel/contents create mode 100644 Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 83833136..b54367e9 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -115,6 +115,7 @@ DDC2E18F26CE25FE0042C5E4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E18E26CE25FE0042C5E4 /* ContentView.swift */; }; DDC2E1A726CEB3400042C5E4 /* LocationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E1A626CEB3400042C5E4 /* LocationHelper.swift */; }; DDC3B274283F411B00AC321C /* LastHeardText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC3B273283F411B00AC321C /* LastHeardText.swift */; }; + DDC4C9FF2A8D982900CE201C /* DetectionSensorConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC4C9FE2A8D982900CE201C /* DetectionSensorConfig.swift */; }; DDC4D568275499A500A4208E /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC4D567275499A500A4208E /* Persistence.swift */; }; DDC94FC129CE063B0082EA6E /* BatteryLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC94FC029CE063B0082EA6E /* BatteryLevel.swift */; }; DDC94FC229CE063B0082EA6E /* BatteryLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC94FC029CE063B0082EA6E /* BatteryLevel.swift */; }; @@ -318,6 +319,8 @@ DDC2E18E26CE25FE0042C5E4 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; DDC2E1A626CEB3400042C5E4 /* LocationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationHelper.swift; sourceTree = ""; }; DDC3B273283F411B00AC321C /* LastHeardText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LastHeardText.swift; sourceTree = ""; }; + DDC4C9FE2A8D982900CE201C /* DetectionSensorConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectionSensorConfig.swift; sourceTree = ""; }; + DDC4CA012A8DAA3800CE201C /* MeshtasticDataModelV16.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV16.xcdatamodel; sourceTree = ""; }; DDC4D567275499A500A4208E /* Persistence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; }; DDC94FC029CE063B0082EA6E /* BatteryLevel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryLevel.swift; sourceTree = ""; }; DDC94FC329CED7280082EA6E /* MeshtasticDataModelV10.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV10.xcdatamodel; sourceTree = ""; }; @@ -517,6 +520,7 @@ DDC94FCD29CF55310082EA6E /* RtttlConfig.swift */, DD6193782863875F00E59241 /* SerialConfig.swift */, DD415827285859C4009B0E59 /* TelemetryConfig.swift */, + DDC4C9FE2A8D982900CE201C /* DetectionSensorConfig.swift */, ); path = Module; sourceTree = ""; @@ -1109,6 +1113,7 @@ DDDB444E29F8AB0E00EE2349 /* Int.swift in Sources */, DD0F791B28713C8A00A6FDAD /* AdminMessageList.swift in Sources */, DD3CC6BC28E366DF00FA9159 /* Meshtastic.xcdatamodeld in Sources */, + DDC4C9FF2A8D982900CE201C /* DetectionSensorConfig.swift in Sources */, DD5E5210298EE33B00D21B61 /* telemetry.pb.swift in Sources */, DD5E5205298EE33B00D21B61 /* mesh.pb.swift in Sources */, DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */, @@ -1622,6 +1627,7 @@ DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + DDC4CA012A8DAA3800CE201C /* MeshtasticDataModelV16.xcdatamodel */, DD14E72C2A80738F006E39BC /* MeshtasticDataModelV15.xcdatamodel */, DD0E9C222A30CE3A00580CBB /* MeshtasticDataModelV14.xcdatamodel */, DDB75A1F2A10766D006ED576 /* MeshtasticDataModelV13.xcdatamodel */, @@ -1638,7 +1644,7 @@ DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */, DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */, ); - currentVersion = DD14E72C2A80738F006E39BC /* MeshtasticDataModelV15.xcdatamodel */; + currentVersion = DDC4CA012A8DAA3800CE201C /* MeshtasticDataModelV16.xcdatamodel */; name = Meshtastic.xcdatamodeld; path = Meshtastic/Meshtastic.xcdatamodeld; sourceTree = ""; diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 4a510d41..05747bb2 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -10,48 +10,6 @@ import CocoaMQTT // --------------------------------------------------------------------------------------- class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate, ObservableObject { - // MqttClientProxyManagerDelegate - func onMqttConnected() { - //mqttManager.status = .connected - mqttProxyConnected = true - print("📲 Mqtt Client Proxy onMqttConnected now subscribing to \(mqttManager.topic).") - mqttManager.mqttClientProxy?.subscribe(mqttManager.topic) - } - - func onMqttDisconnected() { - // mqttManager.status = .disconnected - mqttProxyConnected = false - print("MQTT Disconnected") - } - - func onMqttMessageReceived(message: CocoaMQTTMessage) { - - print("📲 Mqtt Client Proxy onMqttMessageReceived for topic: \(message.topic)") - if message.topic.contains("/stat/") { - return - } - var proxyMessage = MqttClientProxyMessage() - proxyMessage.topic = message.topic - proxyMessage.data = Data(message.payload) - proxyMessage.retained = message.retained - - var toRadio: ToRadio! - toRadio = ToRadio() - toRadio.mqttClientProxyMessage = proxyMessage - let binaryData: Data = try! toRadio.serializedData() - if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected { - connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) - print("📲 Sent Mqtt client proxy message to the connected device.") - } - - } - - func onMqttError(message: String) { - mqttProxyConnected = false - print("MQTT Error") - } - - private static var documentsFolder: URL { do { return try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) @@ -316,6 +274,45 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } } + // MARK: MqttClientProxyManagerDelegate Methods + func onMqttConnected() { + mqttProxyConnected = true + print("📲 Mqtt Client Proxy onMqttConnected now subscribing to \(mqttManager.topic).") + mqttManager.mqttClientProxy?.subscribe(mqttManager.topic) + } + + func onMqttDisconnected() { + mqttProxyConnected = false + print("MQTT Disconnected") + } + + func onMqttMessageReceived(message: CocoaMQTTMessage) { + + print("📲 Mqtt Client Proxy onMqttMessageReceived for topic: \(message.topic)") + if message.topic.contains("/stat/") { + return + } + var proxyMessage = MqttClientProxyMessage() + proxyMessage.topic = message.topic + proxyMessage.data = Data(message.payload) + proxyMessage.retained = message.retained + + var toRadio: ToRadio! + toRadio = ToRadio() + toRadio.mqttClientProxyMessage = proxyMessage + let binaryData: Data = try! toRadio.serializedData() + if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected { + connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) + print("📲 Sent Mqtt client proxy message to the connected device.") + } + } + + func onMqttError(message: String) { + mqttProxyConnected = false + print("📲 Mqtt Client Proxy onMqttError: \(message)") + } + + // MARK: Protobuf Methods func requestDeviceMetadata(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32, context: NSManagedObjectContext) -> Int64 { guard connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected else { return 0 } diff --git a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift index e4a27531..0f840c28 100644 --- a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift +++ b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift @@ -24,10 +24,6 @@ class MqttClientProxyManager { var mqttClientProxy: CocoaMQTT? var topic = "msh/2/c" - private init() { - - } - func connectFromConfigSettings(node: NodeInfoEntity) { let defaultServerAddress = "mqtt.meshtastic.org" @@ -140,7 +136,6 @@ extension MqttClientProxyManager: CocoaMQTTDelegate { } print(errorDescription) delegate?.onMqttError(message: errorDescription) - self.disconnect() } } diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index fdb1b5c8..7ccbfdf1 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModelV15.xcdatamodel + MeshtasticDataModelV16.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV16.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV16.xcdatamodel/contents new file mode 100644 index 00000000..f3885386 --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV16.xcdatamodel/contents @@ -0,0 +1,337 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift new file mode 100644 index 00000000..18ce6479 --- /dev/null +++ b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift @@ -0,0 +1,121 @@ +// +// DetectionSensorModule.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 8/16/23. +// +import SwiftUI + +struct DetectionSensorConfig: View { + + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + @Environment(\.dismiss) private var goBack + var node: NodeInfoEntity? + @State private var isPresentingSaveConfirm: Bool = false + @State var hasChanges: Bool = false + @State var enabled = false + + var body: some View { + + Form { + if node != nil && node?.metadata == nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 { + Text("There has been no response to a request for device metadata over the admin channel for this node.") + .font(.callout) + .foregroundColor(.orange) + + } else if node != nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 { + // Let users know what is going on if they are using remote admin and don't have the config yet + if node?.mqttConfig == nil { + Text("Detection Sensor config data was requested over the admin channel but no response has been returned from the remote node. You can check the status of admin message requests in the admin message log.") + .font(.callout) + .foregroundColor(.orange) + } else { + Text("Remote administration for: \(node?.user?.longName ?? "Unknown")") + .font(.title3) + .onAppear { + setDetectionSensorValues() + } + } + } else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 { + Text("Configuration for: \(node?.user?.longName ?? "Unknown")") + .font(.title3) + } else { + Text("Please connect to a radio to configure settings.") + .font(.callout) + .foregroundColor(.orange) + } + Section(header: Text("options")) { + Toggle(isOn: $enabled) { + Label("enabled", systemImage: "dot.radiowaves.right") + } + } + } + .scrollDismissesKeyboard(.interactively) + .disabled(self.bleManager.connectedPeripheral == nil || node?.mqttConfig == nil) + + Button { + isPresentingSaveConfirm = true + } label: { + Label("save", systemImage: "square.and.arrow.down") + } + .disabled(bleManager.connectedPeripheral == nil || !hasChanges) + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .confirmationDialog( + "are.you.sure", + isPresented: $isPresentingSaveConfirm, + titleVisibility: .visible + ) { + let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) + if connectedNode != nil { + let nodeName = node?.user?.longName ?? "unknown".localized + let buttonText = String.localizedStringWithFormat("save.config %@".localized, nodeName) + Button(buttonText) { + var dsc = DetectionSensorConfig() + dsc.enabled = self.enabled +// let adminMessageId = bleManager.saveMQTTConfig(config: mqtt, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) +// if adminMessageId > 0 { +// // Should show a saved successfully alert once I know that to be true +// // for now just disable the button after a successful save +// hasChanges = false +// goBack() +// } + } + } + } + message: { + Text("config.save.confirm") + } + .navigationTitle("mqtt.config") + .navigationBarItems(trailing: + ZStack { + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") + }) + .onAppear { + self.bleManager.context = context + setDetectionSensorValues() + + // Need to request a TelemetryModuleConfig from the remote node before allowing changes + if bleManager.connectedPeripheral != nil && node?.mqttConfig == nil { + print("empty mqtt module config") + let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) + if node != nil && connectedNode != nil { + _ = bleManager.requestMqttModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + } + } + } + .onChange(of: enabled) { newEnabled in + if node != nil && node?.detectionSensorConfig != nil { + if newEnabled != node!.detectionSensorConfig!.enabled { hasChanges = true } + } + } + } + + func setDetectionSensorValues() { + self.enabled = (node?.detectionSensorConfig?.enabled ?? false) + self.hasChanges = false + } +} diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index e0a6b5cd..5b36fe6d 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -31,6 +31,7 @@ struct Settings: View { case networkConfig case positionConfig case cannedMessagesConfig + case detectionSensorConfig case externalNotificationConfig case mqttConfig case rangeTestConfig @@ -203,6 +204,17 @@ struct Settings: View { } .tag(SettingsSidebar.cannedMessagesConfig) + NavigationLink { + DetectionSensorConfig(node: nodes.first(where: { $0.num == selectedNode })) + } label: { + + Image(systemName: "sensor") + .symbolRenderingMode(.hierarchical) + + Text("detection.sensor") + } + .tag(SettingsSidebar.detectionSensorConfig) + NavigationLink { ExternalNotificationConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { From 39c802dd69b0a5d94066933c809240f91c1b7a5e Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 17 Aug 2023 08:46:10 -0700 Subject: [PATCH 08/40] Update protos --- .../Protobufs/meshtastic/admin.pb.swift | 8 + .../Protobufs/meshtastic/config.pb.swift | 16 +- .../Protobufs/meshtastic/localonly.pb.swift | 38 +++++ Meshtastic/Protobufs/meshtastic/mesh.pb.swift | 10 +- .../meshtastic/module_config.pb.swift | 161 ++++++++++++++++++ 5 files changed, 217 insertions(+), 16 deletions(-) diff --git a/Meshtastic/Protobufs/meshtastic/admin.pb.swift b/Meshtastic/Protobufs/meshtastic/admin.pb.swift index 88ab0f6d..50934353 100644 --- a/Meshtastic/Protobufs/meshtastic/admin.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/admin.pb.swift @@ -742,6 +742,10 @@ struct AdminMessage { /// /// TODO: REPLACE case ambientlightingConfig // = 10 + + /// + /// TODO: REPLACE + case detectionsensorConfig // = 11 case UNRECOGNIZED(Int) init() { @@ -761,6 +765,7 @@ struct AdminMessage { case 8: self = .remotehardwareConfig case 9: self = .neighborinfoConfig case 10: self = .ambientlightingConfig + case 11: self = .detectionsensorConfig default: self = .UNRECOGNIZED(rawValue) } } @@ -778,6 +783,7 @@ struct AdminMessage { case .remotehardwareConfig: return 8 case .neighborinfoConfig: return 9 case .ambientlightingConfig: return 10 + case .detectionsensorConfig: return 11 case .UNRECOGNIZED(let i): return i } } @@ -816,6 +822,7 @@ extension AdminMessage.ModuleConfigType: CaseIterable { .remotehardwareConfig, .neighborinfoConfig, .ambientlightingConfig, + .detectionsensorConfig, ] } @@ -1428,6 +1435,7 @@ extension AdminMessage.ModuleConfigType: SwiftProtobuf._ProtoNameProviding { 8: .same(proto: "REMOTEHARDWARE_CONFIG"), 9: .same(proto: "NEIGHBORINFO_CONFIG"), 10: .same(proto: "AMBIENTLIGHTING_CONFIG"), + 11: .same(proto: "DETECTIONSENSOR_CONFIG"), ] } diff --git a/Meshtastic/Protobufs/meshtastic/config.pb.swift b/Meshtastic/Protobufs/meshtastic/config.pb.swift index 2b7b5c1d..f327d250 100644 --- a/Meshtastic/Protobufs/meshtastic/config.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/config.pb.swift @@ -368,7 +368,7 @@ struct Config { var broadcastSmartMinimumDistance: UInt32 = 0 /// - /// The minumum number of seconds (since the last send) before we can send a position to the mesh if position_broadcast_smart_enabled + /// The minimum number of seconds (since the last send) before we can send a position to the mesh if position_broadcast_smart_enabled var broadcastSmartMinimumIntervalSecs: UInt32 = 0 var unknownFields = SwiftProtobuf.UnknownStorage() @@ -506,14 +506,6 @@ struct Config { /// 0 for default of 1 minute var waitBluetoothSecs: UInt32 = 0 - /// - /// Deprecated in 2.1.X - /// Mesh Super Deep Sleep Timeout Seconds - /// While in Light Sleep if this value is exceeded we will lower into super deep sleep - /// for sds_secs (default 1 year) or a button press - /// 0 for default of two hours, MAXUINT for disabled - var meshSdsTimeoutSecs: UInt32 = 0 - /// /// Super Deep Sleep Seconds /// While in Light Sleep if mesh_sds_timeout_secs is exceeded we will lower into super deep sleep @@ -1806,7 +1798,6 @@ extension Config.PowerConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImple 2: .standard(proto: "on_battery_shutdown_after_secs"), 3: .standard(proto: "adc_multiplier_override"), 4: .standard(proto: "wait_bluetooth_secs"), - 5: .standard(proto: "mesh_sds_timeout_secs"), 6: .standard(proto: "sds_secs"), 7: .standard(proto: "ls_secs"), 8: .standard(proto: "min_wake_secs"), @@ -1823,7 +1814,6 @@ extension Config.PowerConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImple case 2: try { try decoder.decodeSingularUInt32Field(value: &self.onBatteryShutdownAfterSecs) }() case 3: try { try decoder.decodeSingularFloatField(value: &self.adcMultiplierOverride) }() case 4: try { try decoder.decodeSingularUInt32Field(value: &self.waitBluetoothSecs) }() - case 5: try { try decoder.decodeSingularUInt32Field(value: &self.meshSdsTimeoutSecs) }() case 6: try { try decoder.decodeSingularUInt32Field(value: &self.sdsSecs) }() case 7: try { try decoder.decodeSingularUInt32Field(value: &self.lsSecs) }() case 8: try { try decoder.decodeSingularUInt32Field(value: &self.minWakeSecs) }() @@ -1846,9 +1836,6 @@ extension Config.PowerConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImple if self.waitBluetoothSecs != 0 { try visitor.visitSingularUInt32Field(value: self.waitBluetoothSecs, fieldNumber: 4) } - if self.meshSdsTimeoutSecs != 0 { - try visitor.visitSingularUInt32Field(value: self.meshSdsTimeoutSecs, fieldNumber: 5) - } if self.sdsSecs != 0 { try visitor.visitSingularUInt32Field(value: self.sdsSecs, fieldNumber: 6) } @@ -1869,7 +1856,6 @@ extension Config.PowerConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImple if lhs.onBatteryShutdownAfterSecs != rhs.onBatteryShutdownAfterSecs {return false} if lhs.adcMultiplierOverride != rhs.adcMultiplierOverride {return false} if lhs.waitBluetoothSecs != rhs.waitBluetoothSecs {return false} - if lhs.meshSdsTimeoutSecs != rhs.meshSdsTimeoutSecs {return false} if lhs.sdsSecs != rhs.sdsSecs {return false} if lhs.lsSecs != rhs.lsSecs {return false} if lhs.minWakeSecs != rhs.minWakeSecs {return false} diff --git a/Meshtastic/Protobufs/meshtastic/localonly.pb.swift b/Meshtastic/Protobufs/meshtastic/localonly.pb.swift index 219e3728..1d96373e 100644 --- a/Meshtastic/Protobufs/meshtastic/localonly.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/localonly.pb.swift @@ -233,6 +233,28 @@ struct LocalModuleConfig { /// Clears the value of `neighborInfo`. Subsequent reads from it will return its default value. mutating func clearNeighborInfo() {_uniqueStorage()._neighborInfo = nil} + /// + /// The part of the config that is specific to the Ambient Lighting module + var ambientLighting: ModuleConfig.AmbientLightingConfig { + get {return _storage._ambientLighting ?? ModuleConfig.AmbientLightingConfig()} + set {_uniqueStorage()._ambientLighting = newValue} + } + /// Returns true if `ambientLighting` has been explicitly set. + var hasAmbientLighting: Bool {return _storage._ambientLighting != nil} + /// Clears the value of `ambientLighting`. Subsequent reads from it will return its default value. + mutating func clearAmbientLighting() {_uniqueStorage()._ambientLighting = nil} + + /// + /// The part of the config that is specific to the Detection Sensor module + var detectionSensor: ModuleConfig.DetectionSensorConfig { + get {return _storage._detectionSensor ?? ModuleConfig.DetectionSensorConfig()} + set {_uniqueStorage()._detectionSensor = newValue} + } + /// Returns true if `detectionSensor` has been explicitly set. + var hasDetectionSensor: Bool {return _storage._detectionSensor != nil} + /// Clears the value of `detectionSensor`. Subsequent reads from it will return its default value. + mutating func clearDetectionSensor() {_uniqueStorage()._detectionSensor = nil} + /// /// A version integer used to invalidate old save files when we make /// incompatible changes This integer is set at build time and is private to @@ -395,6 +417,8 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem 9: .same(proto: "audio"), 10: .standard(proto: "remote_hardware"), 11: .standard(proto: "neighbor_info"), + 12: .standard(proto: "ambient_lighting"), + 13: .standard(proto: "detection_sensor"), 8: .same(proto: "version"), ] @@ -409,6 +433,8 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem var _audio: ModuleConfig.AudioConfig? = nil var _remoteHardware: ModuleConfig.RemoteHardwareConfig? = nil var _neighborInfo: ModuleConfig.NeighborInfoConfig? = nil + var _ambientLighting: ModuleConfig.AmbientLightingConfig? = nil + var _detectionSensor: ModuleConfig.DetectionSensorConfig? = nil var _version: UInt32 = 0 static let defaultInstance = _StorageClass() @@ -426,6 +452,8 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem _audio = source._audio _remoteHardware = source._remoteHardware _neighborInfo = source._neighborInfo + _ambientLighting = source._ambientLighting + _detectionSensor = source._detectionSensor _version = source._version } } @@ -456,6 +484,8 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem case 9: try { try decoder.decodeSingularMessageField(value: &_storage._audio) }() case 10: try { try decoder.decodeSingularMessageField(value: &_storage._remoteHardware) }() case 11: try { try decoder.decodeSingularMessageField(value: &_storage._neighborInfo) }() + case 12: try { try decoder.decodeSingularMessageField(value: &_storage._ambientLighting) }() + case 13: try { try decoder.decodeSingularMessageField(value: &_storage._detectionSensor) }() default: break } } @@ -501,6 +531,12 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem try { if let v = _storage._neighborInfo { try visitor.visitSingularMessageField(value: v, fieldNumber: 11) } }() + try { if let v = _storage._ambientLighting { + try visitor.visitSingularMessageField(value: v, fieldNumber: 12) + } }() + try { if let v = _storage._detectionSensor { + try visitor.visitSingularMessageField(value: v, fieldNumber: 13) + } }() } try unknownFields.traverse(visitor: &visitor) } @@ -520,6 +556,8 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem if _storage._audio != rhs_storage._audio {return false} if _storage._remoteHardware != rhs_storage._remoteHardware {return false} if _storage._neighborInfo != rhs_storage._neighborInfo {return false} + if _storage._ambientLighting != rhs_storage._ambientLighting {return false} + if _storage._detectionSensor != rhs_storage._detectionSensor {return false} if _storage._version != rhs_storage._version {return false} return true } diff --git a/Meshtastic/Protobufs/meshtastic/mesh.pb.swift b/Meshtastic/Protobufs/meshtastic/mesh.pb.swift index 3b759f18..959ef2c2 100644 --- a/Meshtastic/Protobufs/meshtastic/mesh.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/mesh.pb.swift @@ -106,6 +106,10 @@ enum HardwareModel: SwiftProtobuf.Enum { /// B&Q Consulting Nano G2 Ultra: https://wiki.uniteng.com/en/meshtastic/nano-g2-ultra case nanoG2Ultra // = 18 + /// + /// LoRAType device: https://loratype.org/ + case loraType // = 19 + /// /// B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station case stationG1 // = 25 @@ -232,6 +236,7 @@ enum HardwareModel: SwiftProtobuf.Enum { case 16: self = .tloraT3S3 case 17: self = .nanoG1Explorer case 18: self = .nanoG2Ultra + case 19: self = .loraType case 25: self = .stationG1 case 26: self = .rak11310 case 32: self = .loraRelayV1 @@ -281,6 +286,7 @@ enum HardwareModel: SwiftProtobuf.Enum { case .tloraT3S3: return 16 case .nanoG1Explorer: return 17 case .nanoG2Ultra: return 18 + case .loraType: return 19 case .stationG1: return 25 case .rak11310: return 26 case .loraRelayV1: return 32 @@ -335,6 +341,7 @@ extension HardwareModel: CaseIterable { .tloraT3S3, .nanoG1Explorer, .nanoG2Ultra, + .loraType, .stationG1, .rak11310, .loraRelayV1, @@ -462,7 +469,7 @@ enum CriticalErrorCode: SwiftProtobuf.Enum { case transmitFailed // = 8 /// - /// We detected that the main CPU voltage dropped below the minumum acceptable value + /// We detected that the main CPU voltage dropped below the minimum acceptable value case brownout // = 9 /// Selftest of SX1262 radio chip failed @@ -2496,6 +2503,7 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding { 16: .same(proto: "TLORA_T3_S3"), 17: .same(proto: "NANO_G1_EXPLORER"), 18: .same(proto: "NANO_G2_ULTRA"), + 19: .same(proto: "LORA_TYPE"), 25: .same(proto: "STATION_G1"), 26: .same(proto: "RAK11310"), 32: .same(proto: "LORA_RELAY_V1"), diff --git a/Meshtastic/Protobufs/meshtastic/module_config.pb.swift b/Meshtastic/Protobufs/meshtastic/module_config.pb.swift index fa696bdc..5fae17a2 100644 --- a/Meshtastic/Protobufs/meshtastic/module_config.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/module_config.pb.swift @@ -194,6 +194,16 @@ struct ModuleConfig { set {payloadVariant = .ambientLighting(newValue)} } + /// + /// TODO: REPLACE + var detectionSensor: ModuleConfig.DetectionSensorConfig { + get { + if case .detectionSensor(let v)? = payloadVariant {return v} + return ModuleConfig.DetectionSensorConfig() + } + set {payloadVariant = .detectionSensor(newValue)} + } + var unknownFields = SwiftProtobuf.UnknownStorage() /// @@ -232,6 +242,9 @@ struct ModuleConfig { /// /// TODO: REPLACE case ambientLighting(ModuleConfig.AmbientLightingConfig) + /// + /// TODO: REPLACE + case detectionSensor(ModuleConfig.DetectionSensorConfig) #if !swift(>=4.1) static func ==(lhs: ModuleConfig.OneOf_PayloadVariant, rhs: ModuleConfig.OneOf_PayloadVariant) -> Bool { @@ -283,6 +296,10 @@ struct ModuleConfig { guard case .ambientLighting(let l) = lhs, case .ambientLighting(let r) = rhs else { preconditionFailure() } return l == r }() + case (.detectionSensor, .detectionSensor): return { + guard case .detectionSensor(let l) = lhs, case .detectionSensor(let r) = rhs else { preconditionFailure() } + return l == r + }() default: return false } } @@ -392,6 +409,57 @@ struct ModuleConfig { init() {} } + /// + /// Detection Sensor Module Config + struct DetectionSensorConfig { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// + /// Whether the Module is enabled + var enabled: Bool = false + + /// + /// Interval in seconds of how often we can send a message to the mesh when a state change is detected + var minimumBroadcastSecs: UInt32 = 0 + + /// + /// Interval in seconds of how often we should send a message to the mesh with the current state regardless of changes + /// When set to 0, only state changes will be broadcasted + /// Works as a sort of status heartbeat for peace of mind + var stateBroadcastSecs: UInt32 = 0 + + /// + /// Send ASCII bell with alert message + /// Useful for triggering ext. notification on bell + var sendBell: Bool = false + + /// + /// Friendly name used to format message sent to mesh + /// Example: A name "Motion" would result in a message "Motion detected" + /// Maximum length of 20 characters + var name: String = String() + + /// + /// GPIO pin to monitor for state changes + var monitorPin: UInt32 = 0 + + /// + /// Whether or not the GPIO pin state detection is triggered on HIGH (1) + /// Otherwise LOW (0) + var detectionTriggeredHigh: Bool = false + + /// + /// Whether or not use INPUT_PULLUP mode for GPIO pin + /// Only applicable if the board uses pull-up resistors on the pin + var usePullup: Bool = false + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + } + /// /// Audio Config for codec2 voice struct AudioConfig { @@ -1080,6 +1148,7 @@ extension ModuleConfig.OneOf_PayloadVariant: @unchecked Sendable {} extension ModuleConfig.MQTTConfig: @unchecked Sendable {} extension ModuleConfig.RemoteHardwareConfig: @unchecked Sendable {} extension ModuleConfig.NeighborInfoConfig: @unchecked Sendable {} +extension ModuleConfig.DetectionSensorConfig: @unchecked Sendable {} extension ModuleConfig.AudioConfig: @unchecked Sendable {} extension ModuleConfig.AudioConfig.Audio_Baud: @unchecked Sendable {} extension ModuleConfig.SerialConfig: @unchecked Sendable {} @@ -1121,6 +1190,7 @@ extension ModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat 9: .standard(proto: "remote_hardware"), 10: .standard(proto: "neighbor_info"), 11: .standard(proto: "ambient_lighting"), + 12: .standard(proto: "detection_sensor"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -1272,6 +1342,19 @@ extension ModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat self.payloadVariant = .ambientLighting(v) } }() + case 12: try { + var v: ModuleConfig.DetectionSensorConfig? + var hadOneofValue = false + if let current = self.payloadVariant { + hadOneofValue = true + if case .detectionSensor(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.payloadVariant = .detectionSensor(v) + } + }() default: break } } @@ -1327,6 +1410,10 @@ extension ModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat guard case .ambientLighting(let v)? = self.payloadVariant else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 11) }() + case .detectionSensor?: try { + guard case .detectionSensor(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 12) + }() case nil: break } try unknownFields.traverse(visitor: &visitor) @@ -1501,6 +1588,80 @@ extension ModuleConfig.NeighborInfoConfig: SwiftProtobuf.Message, SwiftProtobuf. } } +extension ModuleConfig.DetectionSensorConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = ModuleConfig.protoMessageName + ".DetectionSensorConfig" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "enabled"), + 2: .standard(proto: "minimum_broadcast_secs"), + 3: .standard(proto: "state_broadcast_secs"), + 4: .standard(proto: "send_bell"), + 5: .same(proto: "name"), + 6: .standard(proto: "monitor_pin"), + 7: .standard(proto: "detection_triggered_high"), + 8: .standard(proto: "use_pullup"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBoolField(value: &self.enabled) }() + case 2: try { try decoder.decodeSingularUInt32Field(value: &self.minimumBroadcastSecs) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self.stateBroadcastSecs) }() + case 4: try { try decoder.decodeSingularBoolField(value: &self.sendBell) }() + case 5: try { try decoder.decodeSingularStringField(value: &self.name) }() + case 6: try { try decoder.decodeSingularUInt32Field(value: &self.monitorPin) }() + case 7: try { try decoder.decodeSingularBoolField(value: &self.detectionTriggeredHigh) }() + case 8: try { try decoder.decodeSingularBoolField(value: &self.usePullup) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.enabled != false { + try visitor.visitSingularBoolField(value: self.enabled, fieldNumber: 1) + } + if self.minimumBroadcastSecs != 0 { + try visitor.visitSingularUInt32Field(value: self.minimumBroadcastSecs, fieldNumber: 2) + } + if self.stateBroadcastSecs != 0 { + try visitor.visitSingularUInt32Field(value: self.stateBroadcastSecs, fieldNumber: 3) + } + if self.sendBell != false { + try visitor.visitSingularBoolField(value: self.sendBell, fieldNumber: 4) + } + if !self.name.isEmpty { + try visitor.visitSingularStringField(value: self.name, fieldNumber: 5) + } + if self.monitorPin != 0 { + try visitor.visitSingularUInt32Field(value: self.monitorPin, fieldNumber: 6) + } + if self.detectionTriggeredHigh != false { + try visitor.visitSingularBoolField(value: self.detectionTriggeredHigh, fieldNumber: 7) + } + if self.usePullup != false { + try visitor.visitSingularBoolField(value: self.usePullup, fieldNumber: 8) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: ModuleConfig.DetectionSensorConfig, rhs: ModuleConfig.DetectionSensorConfig) -> Bool { + if lhs.enabled != rhs.enabled {return false} + if lhs.minimumBroadcastSecs != rhs.minimumBroadcastSecs {return false} + if lhs.stateBroadcastSecs != rhs.stateBroadcastSecs {return false} + if lhs.sendBell != rhs.sendBell {return false} + if lhs.name != rhs.name {return false} + if lhs.monitorPin != rhs.monitorPin {return false} + if lhs.detectionTriggeredHigh != rhs.detectionTriggeredHigh {return false} + if lhs.usePullup != rhs.usePullup {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension ModuleConfig.AudioConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { static let protoMessageName: String = ModuleConfig.protoMessageName + ".AudioConfig" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ From 9a5579a2cf63354e2c5d6619375671a9f21211b4 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 17 Aug 2023 14:26:57 -0500 Subject: [PATCH 09/40] Added thebenternify.sh for easier signing --- thebenternify.sh | 4 ++++ 1 file changed, 4 insertions(+) create mode 100755 thebenternify.sh diff --git a/thebenternify.sh b/thebenternify.sh new file mode 100755 index 00000000..2c9cc497 --- /dev/null +++ b/thebenternify.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +sed -i '' -e 's/GCH7VS5Y9R/6YF6QJH524/g' ./Meshtastic.xcodeproj/project.pbxproj +sed -i '' -e 's/gvh.Meshtastic/thebentern.Meshtastic/g' ./Meshtastic.xcodeproj/project.pbxproj From 70014a7b3fa8bf3e7c1620c6115aca99b700b2b9 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 17 Aug 2023 16:30:48 -0500 Subject: [PATCH 10/40] Added detection sensor config view and core data --- Meshtastic/Helpers/BLEManager.swift | 54 +++++++ .../contents | 7 + Meshtastic/Persistence/UpdateCoreData.swift | 61 +++++++ .../Config/Module/DetectionSensorConfig.swift | 152 ++++++++++++++++-- en.lproj/Localizable.strings | 2 + 5 files changed, 260 insertions(+), 16 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 05747bb2..d1298f4e 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -1534,6 +1534,32 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate return 0 } + public func saveDetectionSensorModuleConfig(config: ModuleConfig.DetectionSensorConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { + + var adminPacket = AdminMessage() + adminPacket.setModuleConfig.detectionSensor = config + + var meshPacket: MeshPacket = MeshPacket() + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { var adminPacket = AdminMessage() @@ -1902,6 +1928,34 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate return false } + public func requestDetectionSensorModuleConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { + + var adminPacket = AdminMessage() + adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.detectionsensorConfig + + var meshPacket: MeshPacket = MeshPacket() + meshPacket.to = UInt32(toUser.num) + meshPacket.from = UInt32(fromUser.num) + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { var adminPacket = AdminMessage() diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV16.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV16.xcdatamodel/contents index f3885386..87e34883 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV16.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV16.xcdatamodel/contents @@ -40,7 +40,14 @@ + + + + + + + diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index eb760bc0..5270461f 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -972,3 +972,64 @@ func upsertTelemetryModuleConfigPacket(config: Meshtastic.ModuleConfig.Telemetry print("💥 Fetching node for core data TelemetryConfigEntity failed: \(nsError)") } } + +func upsertDetectionSensorModuleConfigPacket(config: Meshtastic.ModuleConfig.DetectionSensorConfig, nodeNum: Int64, context: NSManagedObjectContext) { + + let logString = String.localizedStringWithFormat("mesh.log.detectionsensor.config %@".localized, String(nodeNum)) + MeshLogger.log("📈 \(logString)") + + let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + + guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else { + return + } + // Found a node, save Detection Sensor Config + if !fetchedNode.isEmpty { + + if fetchedNode[0].detectionSensorConfig == nil { + + let newConfig = DetectionSensorConfigEntity(context: context) + newConfig.enabled = config.enabled + newConfig.sendBell = config.sendBell + newConfig.name = config.name + + newConfig.monitorPin = Int32(config.monitorPin) + newConfig.detectionTriggeredHigh = config.detectionTriggeredHigh + newConfig.usePullup = config.usePullup + newConfig.minimumBroadcastSecs = Int32(config.minimumBroadcastSecs) + newConfig.stateBroadcastSecs = Int32(config.stateBroadcastSecs) + fetchedNode[0].detectionSensorConfig = newConfig + + } else { + fetchedNode[0].detectionSensorConfig?.enabled = config.enabled + fetchedNode[0].detectionSensorConfig?.sendBell = config.sendBell + fetchedNode[0].detectionSensorConfig?.name = config.name + fetchedNode[0].detectionSensorConfig?.monitorPin = Int32(config.monitorPin) + fetchedNode[0].detectionSensorConfig?.usePullup = config.usePullup + fetchedNode[0].detectionSensorConfig?.detectionTriggeredHigh = config.detectionTriggeredHigh + fetchedNode[0].detectionSensorConfig?.minimumBroadcastSecs = Int32(config.minimumBroadcastSecs) + fetchedNode[0].detectionSensorConfig?.stateBroadcastSecs = Int32(config.stateBroadcastSecs) + } + + do { + try context.save() + print("💾 Updated Detection Sensor Module Config for node number: \(String(nodeNum))") + + } catch { + context.rollback() + let nsError = error as NSError + print("💥 Error Updating Core Data DetectionSensorConfigEntity: \(nsError)") + } + + } else { + print("💥 No Nodes found in local database matching node number \(nodeNum) unable to save Detection Sensor Module Config") + } + + } catch { + let nsError = error as NSError + print("💥 Fetching node for core data DetectionSensorConfigEntity failed: \(nsError)") + } +} diff --git a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift index 18ce6479..97b5c60f 100644 --- a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift @@ -15,6 +15,14 @@ struct DetectionSensorConfig: View { @State private var isPresentingSaveConfirm: Bool = false @State var hasChanges: Bool = false @State var enabled = false + /// DetectionSensorModule will sends a bell character with the messages. + @State var sendBell: Bool = false + @State var name: String = "" + @State var detectionTriggeredHigh: Bool = true + @State var usePullup: Bool = false + @State var minimumBroadcastSecs = UInt32(0) + @State var stateBroadcastSecs = UInt32(0) + @State var monitorPin = UInt32(0) var body: some View { @@ -26,7 +34,7 @@ struct DetectionSensorConfig: 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?.mqttConfig == nil { + if node?.detectionSensorConfig == nil { Text("Detection Sensor config data was requested over the admin channel but no response has been returned from the remote node. You can check the status of admin message requests in the admin message log.") .font(.callout) .foregroundColor(.orange) @@ -49,10 +57,72 @@ struct DetectionSensorConfig: View { Toggle(isOn: $enabled) { Label("enabled", systemImage: "dot.radiowaves.right") } + Toggle(isOn: $sendBell) { + Label("Send Bell", systemImage: "bell") + } + TextField("Friendly name (sent for detection alerts text messages)", text: $name, axis: .vertical) + .foregroundColor(.gray) + .autocapitalization(.none) + .disableAutocorrection(true) + .onChange(of: name, perform: { _ in + + let totalBytes = name.utf8.count + // Only mess with the value if it is too big + if totalBytes > 20 { + + let firstNBytes = Data(name.utf8.prefix(20)) + if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { + // Set the shortName back to the last place where it was the right size + name = maxBytesString + } + } + }) + .foregroundColor(.gray) + } + Section(header: Text("Sensor option")) { + Picker("GPIO Pin to monitor", selection: $monitorPin) { + ForEach(0..<46) { + if $0 == 0 { + Text("unset") + } else { + Text("Pin \($0)") + } + } + } + .pickerStyle(DefaultPickerStyle()) + Toggle(isOn: $detectionTriggeredHigh) { + Label("Detection trigger High", systemImage: "dial.high") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + + Toggle(isOn: $usePullup) { + Label("Uses pullup resistor", systemImage: "arrow.up.to.line") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + } + + Section(header: Text("update.interval")) { + Text("Mininum time between detection broadcasts. Default is 45 seconds.") + .font(.caption) + Picker("Minimum time between detection broadcasts", selection: $minimumBroadcastSecs) { + ForEach(UpdateIntervals.allCases) { ui in + Text(ui.description) + } + } + .pickerStyle(DefaultPickerStyle()) + Text("How often to send detection sensor state to mesh regardless of detection. Default is Never.") + .font(.caption) + Picker("State Broadcast Interval", selection: $stateBroadcastSecs) { + ForEach(UpdateIntervals.allCases) { ui in + Text(ui.description) + } + } + .pickerStyle(DefaultPickerStyle()) + } } .scrollDismissesKeyboard(.interactively) - .disabled(self.bleManager.connectedPeripheral == nil || node?.mqttConfig == nil) + .disabled(self.bleManager.connectedPeripheral == nil || node?.detectionSensorConfig == nil) Button { isPresentingSaveConfirm = true @@ -74,22 +144,29 @@ struct DetectionSensorConfig: View { let nodeName = node?.user?.longName ?? "unknown".localized let buttonText = String.localizedStringWithFormat("save.config %@".localized, nodeName) Button(buttonText) { - var dsc = DetectionSensorConfig() + var dsc = ModuleConfig.DetectionSensorConfig() dsc.enabled = self.enabled -// let adminMessageId = bleManager.saveMQTTConfig(config: mqtt, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) -// if adminMessageId > 0 { -// // Should show a saved successfully alert once I know that to be true -// // for now just disable the button after a successful save -// hasChanges = false -// goBack() -// } - } + dsc.sendBell = self.sendBell + dsc.name = self.name + dsc.monitorPin = UInt32(self.monitorPin) + dsc.detectionTriggeredHigh = self.detectionTriggeredHigh + dsc.usePullup = self.usePullup + dsc.minimumBroadcastSecs = UInt32(self.minimumBroadcastSecs) + dsc.stateBroadcastSecs = UInt32(self.stateBroadcastSecs) + + let adminMessageId = bleManager.saveDetectionSensorModuleConfig(config: dsc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + if adminMessageId > 0 { + // Should show a saved successfully alert once I know that to be true + // for now just disable the button after a successful save + hasChanges = false + goBack() + } } } } message: { Text("config.save.confirm") } - .navigationTitle("mqtt.config") + .navigationTitle("detection.sensor.config") .navigationBarItems(trailing: ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") @@ -98,12 +175,12 @@ struct DetectionSensorConfig: View { self.bleManager.context = context setDetectionSensorValues() - // Need to request a TelemetryModuleConfig from the remote node before allowing changes - if bleManager.connectedPeripheral != nil && node?.mqttConfig == nil { - print("empty mqtt module config") + // Need to request a Detection Sensor Module Config from the remote node before allowing changes + if bleManager.connectedPeripheral != nil && node?.detectionSensorConfig == nil { + print("empty detection sensor module config") let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) if node != nil && connectedNode != nil { - _ = bleManager.requestMqttModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + _ = bleManager.requestDetectionSensorModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) } } } @@ -112,10 +189,53 @@ struct DetectionSensorConfig: View { if newEnabled != node!.detectionSensorConfig!.enabled { hasChanges = true } } } + .onChange(of: sendBell) { newSendBell in + if node != nil && node?.detectionSensorConfig != nil { + if newSendBell != node!.detectionSensorConfig!.sendBell { hasChanges = true } + } + } + .onChange(of: detectionTriggeredHigh) { newDetectionTriggeredHigh in + if node != nil && node?.detectionSensorConfig != nil { + if newDetectionTriggeredHigh != node!.detectionSensorConfig!.detectionTriggeredHigh { hasChanges = true } + } + } + .onChange(of: usePullup) { newUsePullup in + if node != nil && node?.detectionSensorConfig != nil { + if newUsePullup != node!.detectionSensorConfig!.usePullup { hasChanges = true } + } + } + .onChange(of: name) { newName in + if node != nil && node?.detectionSensorConfig != nil { + if newName != node!.detectionSensorConfig!.name { hasChanges = true } + } + } + .onChange(of: monitorPin) { newMonitorPin in + if node != nil && node?.detectionSensorConfig != nil { + if newMonitorPin != node!.detectionSensorConfig!.monitorPin { hasChanges = true } + } + } + .onChange(of: minimumBroadcastSecs) { newMinimumBroadcastSecs in + if node != nil && node?.detectionSensorConfig != nil { + if newMinimumBroadcastSecs != node!.detectionSensorConfig!.minimumBroadcastSecs { hasChanges = true } + } + } + .onChange(of: stateBroadcastSecs) { newStateBroadcastSecs in + if node != nil && node?.detectionSensorConfig != nil { + if newStateBroadcastSecs != node!.detectionSensorConfig!.stateBroadcastSecs { hasChanges = true } + } + } } func setDetectionSensorValues() { self.enabled = (node?.detectionSensorConfig?.enabled ?? false) + self.sendBell = (node?.detectionSensorConfig?.sendBell ?? false) + self.name = (node?.detectionSensorConfig?.name ?? "") + self.monitorPin = UInt32(node?.detectionSensorConfig?.monitorPin ?? 0) + self.usePullup = (node?.detectionSensorConfig?.usePullup ?? false) + self.detectionTriggeredHigh = (node?.detectionSensorConfig?.detectionTriggeredHigh ?? true) + self.minimumBroadcastSecs = UInt32(node?.detectionSensorConfig?.minimumBroadcastSecs ?? 45) + self.stateBroadcastSecs = UInt32(node?.detectionSensorConfig?.stateBroadcastSecs ?? 0) + self.hasChanges = false } } diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index a7b870a6..6ab770c4 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -57,6 +57,8 @@ "current"="Current"; "default"="Default"; "delete"="Delete"; +"detection.sensor"="Detection Sensor"; +"detection.sensor.config"="Detection Sensor Config"; "device"="Device"; "device.config"="Device Config"; "device.metrics.delete"="Delete all device metrics?"; From 8fe319792823c5e552730b6019bc73f269a5c7a1 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 17 Aug 2023 17:00:20 -0700 Subject: [PATCH 11/40] Fix up default picker values --- .../Config/Module/DetectionSensorConfig.swift | 31 ++++++++++--------- .../Settings/Config/Module/MQTTConfig.swift | 2 +- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift index 97b5c60f..8015b3fd 100644 --- a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift @@ -20,9 +20,9 @@ struct DetectionSensorConfig: View { @State var name: String = "" @State var detectionTriggeredHigh: Bool = true @State var usePullup: Bool = false - @State var minimumBroadcastSecs = UInt32(0) - @State var stateBroadcastSecs = UInt32(0) - @State var monitorPin = UInt32(0) + @State var minimumBroadcastSecs = 0 + @State var stateBroadcastSecs = 0 + @State var monitorPin = 0 var body: some View { @@ -102,22 +102,25 @@ struct DetectionSensorConfig: View { } Section(header: Text("update.interval")) { + Picker("Minimum time between detection broadcasts", selection: $minimumBroadcastSecs) { + Text("Default").tag(0) + ForEach(UpdateIntervals.allCases) { ui in + Text(ui.description).tag(ui.rawValue) + } + } + .pickerStyle(DefaultPickerStyle()) Text("Mininum time between detection broadcasts. Default is 45 seconds.") .font(.caption) - Picker("Minimum time between detection broadcasts", selection: $minimumBroadcastSecs) { + + Picker("State Broadcast Interval", selection: $stateBroadcastSecs) { + Text("Default").tag(0) ForEach(UpdateIntervals.allCases) { ui in - Text(ui.description) + Text(ui.description).tag(ui.rawValue) } } .pickerStyle(DefaultPickerStyle()) Text("How often to send detection sensor state to mesh regardless of detection. Default is Never.") .font(.caption) - Picker("State Broadcast Interval", selection: $stateBroadcastSecs) { - ForEach(UpdateIntervals.allCases) { ui in - Text(ui.description) - } - } - .pickerStyle(DefaultPickerStyle()) } } @@ -230,11 +233,11 @@ struct DetectionSensorConfig: View { self.enabled = (node?.detectionSensorConfig?.enabled ?? false) self.sendBell = (node?.detectionSensorConfig?.sendBell ?? false) self.name = (node?.detectionSensorConfig?.name ?? "") - self.monitorPin = UInt32(node?.detectionSensorConfig?.monitorPin ?? 0) + self.monitorPin = Int(node?.detectionSensorConfig?.monitorPin ?? 0) self.usePullup = (node?.detectionSensorConfig?.usePullup ?? false) self.detectionTriggeredHigh = (node?.detectionSensorConfig?.detectionTriggeredHigh ?? true) - self.minimumBroadcastSecs = UInt32(node?.detectionSensorConfig?.minimumBroadcastSecs ?? 45) - self.stateBroadcastSecs = UInt32(node?.detectionSensorConfig?.stateBroadcastSecs ?? 0) + self.minimumBroadcastSecs = Int(node?.detectionSensorConfig?.minimumBroadcastSecs ?? 45) + self.stateBroadcastSecs = Int(node?.detectionSensorConfig?.stateBroadcastSecs ?? 0) self.hasChanges = false } diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index c7a9d562..11dd82ab 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -187,7 +187,7 @@ struct MQTTConfig: View { Text("The root topic to use for MQTT messages. Default is \"msh\". This is useful if you want to use a single MQTT server for multiple meshtastic networks and separate them via ACLs") .font(.caption2) } - Text("WiFi or Ethernet must also be enabled for MQTT to work. You can set uplink and downlink for each channel.") + Text("You can set uplink and downlink for each channel.") .font(.callout) } .scrollDismissesKeyboard(.interactively) From 55080fa91fec09fbbb45afe89e46dd04946ca967 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 18 Aug 2023 00:12:59 -0700 Subject: [PATCH 12/40] Store and forwarding --- Meshtastic/Helpers/BLEManager.swift | 4 +- Meshtastic/Helpers/MeshPackets.swift | 41 +++++++++++++++++++ .../Config/Module/DetectionSensorConfig.swift | 3 +- de.lproj/Localizable.strings | 1 + en.lproj/Localizable.strings | 1 + zh-Hans.lproj/Localizable.strings | 1 + 6 files changed, 47 insertions(+), 4 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index d1298f4e..8e9d3beb 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -550,7 +550,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate case .serialApp: MeshLogger.log("🕸️ MESH PACKET received for Serial App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") case .storeForwardApp: - MeshLogger.log("🕸️ MESH PACKET received for Store Forward App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + storeAndForwardPacket(packet: decodedInfo.packet, connectedNodeNum: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) case .rangeTestApp: MeshLogger.log("🕸️ MESH PACKET received for Range Test App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") case .telemetryApp: @@ -659,7 +659,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate self.startScanning() // Try and connect to the preferredPeripherial first - let preferredPeripheral = peripherals.filter({ $0.peripheral.identifier.uuidString == UserDefaults.preferredPeripheralId as? String ?? "" }).first + let preferredPeripheral = peripherals.filter({ $0.peripheral.identifier.uuidString == UserDefaults.preferredPeripheralId as String }).first if preferredPeripheral != nil && preferredPeripheral?.peripheral != nil { connectTo(peripheral: preferredPeripheral!.peripheral) } diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 382a4619..d3e5a31c 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -578,6 +578,47 @@ func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSMana } } +func storeAndForwardPacket(packet: MeshPacket, connectedNodeNum: Int64, context: NSManagedObjectContext) { + + if let storeAndForwardMessage = try? StoreAndForward(serializedData: packet.decoded.payload) { + // RequestResponse + switch storeAndForwardMessage.rr { + + case .unset: + MeshLogger.log("📮 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)") + case .routerError: + MeshLogger.log("☠️ Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)") + case .routerHeartbeat: + // Query any messages since the heartbeat.period. Send their ids to the store and forward node. + MeshLogger.log("💓 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)") + case .routerPing: + MeshLogger.log("🏓 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)") + case .routerPong: + MeshLogger.log("🏓 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)") + case .routerBusy: + MeshLogger.log("🐝 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)") + case .routerHistory: + MeshLogger.log("📜 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)") + case .routerStats: + MeshLogger.log("📊 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)") + case .clientError: + MeshLogger.log("☠️ Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)") + case .clientHistory: + MeshLogger.log("📜 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)") + case .clientStats: + MeshLogger.log("📊 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)") + case .clientPing: + MeshLogger.log("🏓 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)") + case .clientPong: + MeshLogger.log("🏓 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)") + case .clientAbort: + MeshLogger.log("🛑 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)") + case .UNRECOGNIZED(_): + MeshLogger.log("📮 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)") + } + } +} + func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManagedObjectContext) { if let telemetryMessage = try? Telemetry(serializedData: packet.decoded.payload) { diff --git a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift index 8015b3fd..1f7b1b41 100644 --- a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift @@ -103,7 +103,6 @@ struct DetectionSensorConfig: View { Section(header: Text("update.interval")) { Picker("Minimum time between detection broadcasts", selection: $minimumBroadcastSecs) { - Text("Default").tag(0) ForEach(UpdateIntervals.allCases) { ui in Text(ui.description).tag(ui.rawValue) } @@ -113,7 +112,7 @@ struct DetectionSensorConfig: View { .font(.caption) Picker("State Broadcast Interval", selection: $stateBroadcastSecs) { - Text("Default").tag(0) + Text("Never").tag(0) ForEach(UpdateIntervals.allCases) { ui in Text(ui.description).tag(ui.rawValue) } diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index ea8b3b90..16356d1c 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -194,6 +194,7 @@ "name"="Name"; "network"="Netzwerk"; "network.config"="Netzwerkeinstellungen"; +"nodes"="Nodes"; "nodes %@"="Nodes (%@)"; "no.nodes"="Keine Meshtastic Nodes gefunden"; "not.connected"="Kein Gerät verbunden"; diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index 6ab770c4..b6166ab2 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -196,6 +196,7 @@ "name"="Name"; "network"="Network"; "network.config"="Network Config"; +"nodes"="Nodes"; "nodes %@"="Nodes (%@)"; "no.nodes"="No Meshtastic Nodes Found"; "not.connected"="No device connected"; diff --git a/zh-Hans.lproj/Localizable.strings b/zh-Hans.lproj/Localizable.strings index dac0df52..9ec1cc5d 100644 --- a/zh-Hans.lproj/Localizable.strings +++ b/zh-Hans.lproj/Localizable.strings @@ -194,6 +194,7 @@ "name"="名称"; "network"="网络"; "network.config"="网络配置"; +"nodes"="节点"; "nodes %@"="节点 (%@)"; "no.nodes"="未找到 Meshtastic 节点"; "not.connected"="未连接到电台"; From 2c8ec9cd8c698a3be0cf53b0f5d71e7fb5007ed4 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 18 Aug 2023 09:20:08 -0500 Subject: [PATCH 13/40] Add detection sensor module to want_config / admin messages plumbing --- Meshtastic/Helpers/MeshPackets.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index d3e5a31c..3569b8e3 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -68,6 +68,8 @@ func moduleConfig (config: ModuleConfig, context: NSManagedObjectContext, nodeNu upsertSerialModuleConfigPacket(config: config.serial, nodeNum: nodeNum, context: context) } else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.telemetry(config.telemetry) { upsertTelemetryModuleConfigPacket(config: config.telemetry, nodeNum: nodeNum, context: context) + } else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.detectionSensor(config.detectionSensor) { + upsertDetectionSensorModuleConfigPacket(config: config.detectionSensor, nodeNum: nodeNum, context: context) } } @@ -469,7 +471,8 @@ func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.telemetry(moduleConfig.telemetry) { upsertTelemetryModuleConfigPacket(config: moduleConfig.telemetry, nodeNum: Int64(packet.from), context: context) - + } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.detectionSensor(moduleConfig.detectionSensor) { + upsertDetectionSensorModuleConfigPacket(config: moduleConfig.detectionSensor, nodeNum: Int64(packet.from), context: context) } } else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getRingtoneResponse(adminMessage.getRingtoneResponse) { From 5bb162764e4198bcb1fb12b26711f6c68e3f8bb9 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 18 Aug 2023 13:31:03 -0500 Subject: [PATCH 14/40] Update protos --- Meshtastic/Protobufs/meshtastic/portnums.pb.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Meshtastic/Protobufs/meshtastic/portnums.pb.swift b/Meshtastic/Protobufs/meshtastic/portnums.pb.swift index 5cdd6cd0..711a990e 100644 --- a/Meshtastic/Protobufs/meshtastic/portnums.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/portnums.pb.swift @@ -102,6 +102,10 @@ enum PortNum: SwiftProtobuf.Enum { /// This marker comes from the 'moduleConfig.audio.bitrate' enum minus one. case audioApp // = 9 + /// + /// Same as Text Message but originating from Detection Sensor Module. + case detectionSensorApp // = 10 + /// /// Provides a 'ping' service that replies to any packet it receives. /// Also serves as a small example module. @@ -197,6 +201,7 @@ enum PortNum: SwiftProtobuf.Enum { case 7: self = .textMessageCompressedApp case 8: self = .waypointApp case 9: self = .audioApp + case 10: self = .detectionSensorApp case 32: self = .replyApp case 33: self = .ipTunnelApp case 64: self = .serialApp @@ -226,6 +231,7 @@ enum PortNum: SwiftProtobuf.Enum { case .textMessageCompressedApp: return 7 case .waypointApp: return 8 case .audioApp: return 9 + case .detectionSensorApp: return 10 case .replyApp: return 32 case .ipTunnelApp: return 33 case .serialApp: return 64 @@ -260,6 +266,7 @@ extension PortNum: CaseIterable { .textMessageCompressedApp, .waypointApp, .audioApp, + .detectionSensorApp, .replyApp, .ipTunnelApp, .serialApp, @@ -296,6 +303,7 @@ extension PortNum: SwiftProtobuf._ProtoNameProviding { 7: .same(proto: "TEXT_MESSAGE_COMPRESSED_APP"), 8: .same(proto: "WAYPOINT_APP"), 9: .same(proto: "AUDIO_APP"), + 10: .same(proto: "DETECTION_SENSOR_APP"), 32: .same(proto: "REPLY_APP"), 33: .same(proto: "IP_TUNNEL_APP"), 64: .same(proto: "SERIAL_APP"), From a16ad4aa47bdc922fe94ed56362aa2f81ee849b6 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 18 Aug 2023 13:31:24 -0500 Subject: [PATCH 15/40] Handle detectionSensorApp payloads like text messages --- Meshtastic/Helpers/BLEManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 8e9d3beb..253ee792 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -529,7 +529,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate // Log any other unknownApp calls if !nowKnown { MeshLogger.log("🕸️ MESH PACKET received for Unknown App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") } - case .textMessageApp: + case .textMessageApp, .detectionSensorApp: textMessageAppPacket(packet: decodedInfo.packet, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) case .remoteHardwareApp: MeshLogger.log("🕸️ MESH PACKET received for Remote Hardware App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") From 38e9274f874bc15a36b1c08e3c3733af2739e553 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 19 Aug 2023 19:38:01 -0500 Subject: [PATCH 16/40] Add wantRangeTestPackets to treat rangetest packets like text when enabled --- Meshtastic/Helpers/BLEManager.swift | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 253ee792..e7a99c9f 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -40,6 +40,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var lastPosition: CLLocationCoordinate2D? let emptyNodeNum: UInt32 = 4294967295 let mqttManager = MqttClientProxyManager.shared + var wantRangeTestPackets = false /* Meshtastic Service Details */ var TORADIO_characteristic: CBCharacteristic! var FROMRADIO_characteristic: CBCharacteristic! @@ -552,7 +553,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate case .storeForwardApp: storeAndForwardPacket(packet: decodedInfo.packet, connectedNodeNum: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) case .rangeTestApp: - MeshLogger.log("🕸️ MESH PACKET received for Range Test App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + if wantRangeTestPackets { + textMessageAppPacket(packet: decodedInfo.packet, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) + } + else { + MeshLogger.log("🕸️ MESH PACKET received for Range Test App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + } case .telemetryApp: if !invalidVersion { telemetryPacket(packet: decodedInfo.packet, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) } case .textMessageCompressedApp: @@ -600,7 +606,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate peripherals.removeAll(where: { $0.peripheral.state == CBPeripheralState.disconnected }) // Config conplete returns so we don't read the characteristic again - /// MQTT Client Proxy + /// MQTT Client Proxy and RangeTest interest if connectedPeripheral.num > 0 { let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") @@ -608,12 +614,15 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate do { let fetchedNodeInfo = try context?.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] ?? [] if fetchedNodeInfo.count == 1 && fetchedNodeInfo[0].mqttConfig != nil { - //Subscribe to Mqtt Client Proxy if enabled if fetchedNodeInfo[0].mqttConfig?.proxyToClientEnabled ?? false { mqttManager.connectFromConfigSettings(node: fetchedNodeInfo[0]) } } + if fetchedNodeInfo.count == 1 && fetchedNodeInfo[0].rangeTestConfig?.enabled == true { + wantRangeTestPackets = true; + } + } catch { print("Failed to find a node info for the connected node") } From ab81e2f4e6b135ad5a8e0358a3e4355095d96e3f Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 20 Aug 2023 07:59:15 -0500 Subject: [PATCH 17/40] Fix swift warning for non-nil string --- Meshtastic/Helpers/BLEManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index e7a99c9f..268b64af 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -505,7 +505,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if decodedInfo.metadata.firmwareVersion.count > 0 && !invalidVersion { nowKnown = true deviceMetadataPacket(metadata: decodedInfo.metadata, fromNum: connectedPeripheral.num, context: context!) - connectedPeripheral.firmwareVersion = decodedInfo.metadata.firmwareVersion ?? "unknown".localized + connectedPeripheral.firmwareVersion = decodedInfo.metadata.firmwareVersion let lastDotIndex = decodedInfo.metadata.firmwareVersion.lastIndex(of: ".") From f2e32886316eec01ff2be6b6aa771b1cd856675e Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 20 Aug 2023 15:59:39 -0500 Subject: [PATCH 18/40] Basic local notification for Waypoint packets --- Meshtastic/Helpers/MeshPackets.swift | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 3569b8e3..062bddbd 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -867,7 +867,19 @@ func waypointPacket (packet: MeshPacket, context: NSManagedObjectContext) { waypoint.created = Date() do { try context.save() - print("💾 Updated Node Waypoint App Packet For: \(waypoint.id)") + print("💾 Added Node Waypoint App Packet For: \(waypoint.id)") + let manager = LocalNotificationManager() + let icon = String(UnicodeScalar(Int(waypoint.icon)) ?? "📍") + let latitude = Double(waypoint.latitudeI) / 1e7 + let longitude = Double(waypoint.longitudeI) / 1e7 + manager.notifications = [ + Notification( + id: ("notification.id.\(waypoint.id)"), + title: "New Waypoint Received", + subtitle: "\(icon) \(waypoint.name ?? "Dropped Pin")", + content: "\(waypoint.longDescription ?? "\(latitude), \(longitude)")") + ] + manager.schedule() } catch { context.rollback() let nsError = error as NSError From 66298d52c78bcdf6508b5147334ac86bd10a6ed5 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 21 Aug 2023 10:24:17 -0500 Subject: [PATCH 19/40] Add AppDelegate and shared AppState object to control navigation programatically --- Meshtastic.xcodeproj/project.pbxproj | 4 ++ .../Helpers/LocalNotificationManager.swift | 10 +++-- Meshtastic/Helpers/MeshPackets.swift | 4 +- Meshtastic/MeshtasticApp.swift | 18 ++++++--- Meshtastic/MeshtasticAppDelegate.swift | 37 +++++++++++++++++++ Meshtastic/Views/ContentView.swift | 28 +++++++------- 6 files changed, 76 insertions(+), 25 deletions(-) create mode 100644 Meshtastic/MeshtasticAppDelegate.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index b54367e9..bf5f71ba 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 6DA39D8E2A92DC52007E311C /* MeshtasticAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */; }; C9697F9D279336B700250207 /* LocalMBTileOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */; }; C9697FA527933B8C00250207 /* SQLite in Frameworks */ = {isa = PBXBuildFile; productRef = C9697FA427933B8C00250207 /* SQLite */; }; DD0D3D222A55CEB10066DB71 /* CocoaMQTT in Frameworks */ = {isa = PBXBuildFile; productRef = DD0D3D212A55CEB10066DB71 /* CocoaMQTT */; }; @@ -195,6 +196,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticAppDelegate.swift; sourceTree = ""; }; A65FA974296876BF00A97686 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMBTileOverlay.swift; sourceTree = ""; }; DD0E9C222A30CE3A00580CBB /* MeshtasticDataModelV14.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV14.xcdatamodel; sourceTree = ""; }; @@ -625,6 +627,7 @@ DDC2E15726CE248E0042C5E4 /* MeshtasticApp.swift */, DDC2E16526CE248F0042C5E4 /* Info.plist */, DDC2E15D26CE248F0042C5E4 /* Preview Content */, + 6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */, ); path = Meshtastic; sourceTree = ""; @@ -1058,6 +1061,7 @@ DD5E520D298EE33B00D21B61 /* storeforward.pb.swift in Sources */, DD882F5D2772E4640005BF05 /* Contacts.swift in Sources */, DD964FC2297272AE007C176F /* WaypointEntityExtension.swift in Sources */, + 6DA39D8E2A92DC52007E311C /* MeshtasticAppDelegate.swift in Sources */, DD47E3CE26F103C600029299 /* NodeList.swift in Sources */, DD5E520A298EE33B00D21B61 /* channel.pb.swift in Sources */, DDDEE5E129DA3E1100A8E078 /* NodeInfoView.swift in Sources */, diff --git a/Meshtastic/Helpers/LocalNotificationManager.swift b/Meshtastic/Helpers/LocalNotificationManager.swift index d6bea32b..c5efe48e 100644 --- a/Meshtastic/Helpers/LocalNotificationManager.swift +++ b/Meshtastic/Helpers/LocalNotificationManager.swift @@ -10,16 +10,16 @@ class LocalNotificationManager { UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in if granted == true && error == nil { - self.scheduleNotifications() + self.scheduleNotifications() } } } - func schedule() { + func schedule() { UNUserNotificationCenter.current().getNotificationSettings { settings in switch settings.authorizationStatus { case .notDetermined: - self.requestAuthorization() + self.requestAuthorization() case .authorized, .provisional: self.scheduleNotifications() default: @@ -37,6 +37,9 @@ class LocalNotificationManager { content.body = notification.content content.sound = .default content.interruptionLevel = .timeSensitive + if notification.target != nil { + content.userInfo["target"] = notification.target + } let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false) let request = UNNotificationRequest(identifier: notification.id, content: content, trigger: trigger) @@ -65,4 +68,5 @@ struct Notification { var title: String var subtitle: String var content: String + var target: String? } diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 062bddbd..c3384193 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -877,7 +877,9 @@ func waypointPacket (packet: MeshPacket, context: NSManagedObjectContext) { id: ("notification.id.\(waypoint.id)"), title: "New Waypoint Received", subtitle: "\(icon) \(waypoint.name ?? "Dropped Pin")", - content: "\(waypoint.longDescription ?? "\(latitude), \(longitude)")") + content: "\(waypoint.longDescription ?? "\(latitude), \(longitude)")", + target: "waypoint" + ) ] manager.schedule() } catch { diff --git a/Meshtastic/MeshtasticApp.swift b/Meshtastic/MeshtasticApp.swift index 49046122..3325a45b 100644 --- a/Meshtastic/MeshtasticApp.swift +++ b/Meshtastic/MeshtasticApp.swift @@ -4,8 +4,8 @@ import SwiftUI import CoreData @main -struct MeshtasticAppleApp: App { - +struct MeshtasticAppleApp : App { + @UIApplicationDelegateAdaptor(MeshtasticAppDelegate.self) var appDelegate let persistenceController = PersistenceController.shared @ObservedObject private var bleManager: BLEManager = BLEManager() @Environment(\.scenePhase) var scenePhase @@ -13,10 +13,11 @@ struct MeshtasticAppleApp: App { @State var saveChannels = false @State var incomingUrl: URL? @State var channelSettings: String? - + @StateObject var appState = AppState.shared + var body: some Scene { WindowGroup { - ContentView() + ContentView() .environment(\.managedObjectContext, persistenceController.container.viewContext) .environmentObject(bleManager) .sheet(isPresented: $saveChannels) { @@ -45,7 +46,6 @@ struct MeshtasticAppleApp: App { print("Some sort of URL was received \(url)") self.incomingUrl = url - if url.absoluteString.lowercased().contains("meshtastic.org/e/#") { if let components = self.incomingUrl?.absoluteString.components(separatedBy: "#") { self.channelSettings = components.last! @@ -115,5 +115,11 @@ struct MeshtasticAppleApp: App { print("💥 Apple must have changed something") } } - } + } +} + +class AppState: ObservableObject { + static let shared = AppState() + + @Published var tabSelection: Tab = .ble } diff --git a/Meshtastic/MeshtasticAppDelegate.swift b/Meshtastic/MeshtasticAppDelegate.swift new file mode 100644 index 00000000..c04a1b51 --- /dev/null +++ b/Meshtastic/MeshtasticAppDelegate.swift @@ -0,0 +1,37 @@ +// +// MeshtasticAppDelegate.swift +// Meshtastic +// +// Created by Ben on 8/20/23. +// + +import SwiftUI + +class MeshtasticAppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate { + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { + print("App launched!") + UNUserNotificationCenter.current().delegate = self + return true + } + + func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + } + + // This method is called when user clicked on the notification + func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) + { + let userInfo = response.notification.request.content.userInfo + if let targetValue = userInfo["target"] as? String, targetValue == "waypoint" + { + openWaypoint() + } + + completionHandler() + } + + private func openWaypoint() + { + AppState.shared.tabSelection = Tab.map + } +} diff --git a/Meshtastic/Views/ContentView.swift b/Meshtastic/Views/ContentView.swift index 9ca1e2b7..f630d195 100644 --- a/Meshtastic/Views/ContentView.swift +++ b/Meshtastic/Views/ContentView.swift @@ -5,22 +5,11 @@ import SwiftUI struct ContentView: View { - - @State private var selection: Tab = .ble - - enum Tab { - case contacts - case messages - case map - case ble - case nodes - case settings - } - + @StateObject var appState = AppState.shared + var body: some View { - - TabView(selection: $selection) { - + + TabView(selection: $appState.tabSelection) { Contacts() .tabItem { Label("messages", systemImage: "message") @@ -55,3 +44,12 @@ struct ContentView_Previews: PreviewProvider { ContentView() } } + +enum Tab { + case contacts + case messages + case map + case ble + case nodes + case settings +} From fce645c576f2742d89098c9a035ec271ec169dae Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 21 Aug 2023 22:46:34 -0700 Subject: [PATCH 20/40] Add search to nodes list --- Meshtastic/Views/Nodes/NodeList.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index d9a497ef..aa77c2f0 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -12,6 +12,16 @@ import SwiftUI import CoreLocation struct NodeList: View { + + @State private var searchText = "" + var nodesQuery: Binding { + Binding { + searchText + } set: { newValue in + searchText = newValue + nodes.nsPredicate = newValue.isEmpty ? nil : NSPredicate(format: "user.longName CONTAINS %@", newValue) + } + } @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @@ -116,5 +126,7 @@ struct NodeList: View { Text("select.node") } } + .searchable(text: nodesQuery, prompt: "Find a node") } } + From 51813cbd86bf50546d9804d5b1122362b899bd34 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 21 Aug 2023 23:03:30 -0700 Subject: [PATCH 21/40] case insensitve search --- Meshtastic/Views/Nodes/NodeList.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index aa77c2f0..ccb3fd2f 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -19,7 +19,7 @@ struct NodeList: View { searchText } set: { newValue in searchText = newValue - nodes.nsPredicate = newValue.isEmpty ? nil : NSPredicate(format: "user.longName CONTAINS %@", newValue) + nodes.nsPredicate = newValue.isEmpty ? nil : NSPredicate(format: "user.longName CONTAINS[c] %@", newValue) } } From f4e6393baa0aeddbc59407539c5ea458f7676e79 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 21 Aug 2023 23:51:51 -0700 Subject: [PATCH 22/40] Search long and short names --- Meshtastic/Views/Nodes/NodeList.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index ccb3fd2f..793b69c3 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -19,7 +19,7 @@ struct NodeList: View { searchText } set: { newValue in searchText = newValue - nodes.nsPredicate = newValue.isEmpty ? nil : NSPredicate(format: "user.longName CONTAINS[c] %@", newValue) + nodes.nsPredicate = newValue.isEmpty ? nil : NSPredicate(format: "user.longName CONTAINS[c] %@ OR user.shortName CONTAINS[c] %@", newValue, newValue) } } From 4865b61fa64470f55a0380a05bd186453414febc Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 22 Aug 2023 11:18:10 -0500 Subject: [PATCH 23/40] Added portnum to datamodel and represent Detection sensor payloads with icon --- Meshtastic/Helpers/MeshPackets.swift | 1 + .../MeshtasticDataModelV16.xcdatamodel/contents | 1 + Meshtastic/Views/Messages/ChannelMessageList.swift | 12 ++++++++++++ Meshtastic/Views/Nodes/NodeMap.swift | 1 + 4 files changed, 15 insertions(+) diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index c3384193..29583cf4 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -750,6 +750,7 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM newMessage.rssi = packet.rxRssi newMessage.isEmoji = packet.decoded.emoji == 1 newMessage.channel = Int32(packet.channel) + newMessage.portNum = Int32(packet.decoded.portnum.rawValue) if packet.decoded.replyID > 0 { newMessage.replyID = Int64(packet.decoded.replyID) diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV16.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV16.xcdatamodel/contents index 87e34883..1e80b42a 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV16.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV16.xcdatamodel/contents @@ -141,6 +141,7 @@ + diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index c11a02b1..83e30709 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -63,12 +63,24 @@ struct ChannelMessageList: View { VStack(alignment: currentUser ? .trailing : .leading) { let markdownText: LocalizedStringKey = LocalizedStringKey.init(message.messagePayloadMarkdown ?? (message.messagePayload ?? "EMPTY MESSAGE")) let linkBlue = Color(red: 0.4627, green: 0.8392, blue: 1) /* #76d6ff */ + let isDetectionSensorMessage = message.portNum == Int32(PortNum.detectionSensorApp.rawValue) + Text(markdownText) .tint(linkBlue) .padding(10) .foregroundColor(.white) .background(currentUser ? .accentColor : Color(.gray)) .cornerRadius(15) + .overlay( + VStack { + isDetectionSensorMessage ? Image(systemName: "sensor.fill") + .padding() + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing) + .foregroundStyle(Color.orange) + .offset(x: 20, y: -20) + : nil + } + ) .contextMenu { VStack { Text("channel")+Text(": \(message.channel)") diff --git a/Meshtastic/Views/Nodes/NodeMap.swift b/Meshtastic/Views/Nodes/NodeMap.swift index 16043adc..eb830668 100644 --- a/Meshtastic/Views/Nodes/NodeMap.swift +++ b/Meshtastic/Views/Nodes/NodeMap.swift @@ -16,6 +16,7 @@ struct NodeMap: View { @EnvironmentObject var bleManager: BLEManager @ObservedObject var tileManager = OfflineTileManager.shared + @StateObject var appState = AppState.shared @State var selectedMapLayer: MapLayer = UserDefaults.mapLayer @State var enableMapRecentering: Bool = UserDefaults.enableMapRecentering From 23c2df9d993086e66ea30c0fd7f5e1ae58df356b Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 22 Aug 2023 12:00:03 -0500 Subject: [PATCH 24/40] AppState tab selection targets for all local notifications --- Meshtastic/Helpers/MeshPackets.swift | 11 +++++++---- Meshtastic/MeshtasticAppDelegate.swift | 19 ++++++++++--------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 29583cf4..f025093e 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -686,6 +686,7 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage let content = UNMutableNotificationContent() content.title = "Critically Low Battery!" content.body = "Time to charge your radio, there is \(telemetry.batteryLevel)% battery remaining." + content.userInfo["target"] = "node" let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false) let uuidString = UUID().uuidString let request = UNNotificationRequest(identifier: uuidString, content: content, trigger: trigger) @@ -777,7 +778,6 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM messageSaved = true if messageSaved { - if newMessage.fromUser != nil && newMessage.toUser != nil && !(newMessage.fromUser?.mute ?? false) { // Create an iOS Notification for the received DM message and schedule it immediately let manager = LocalNotificationManager() @@ -786,7 +786,9 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM id: ("notification.id.\(newMessage.messageId)"), title: "\(newMessage.fromUser?.longName ?? "unknown".localized)", subtitle: "AKA \(newMessage.fromUser?.shortName ?? "???")", - content: messageText) + content: messageText, + target: "message" + ) ] manager.schedule() print("💬 iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "unknown".localized)") @@ -813,7 +815,8 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM id: ("notification.id.\(newMessage.messageId)"), title: "\(newMessage.fromUser?.longName ?? "unknown".localized)", subtitle: "AKA \(newMessage.fromUser?.shortName ?? "???")", - content: messageText) + content: messageText, + target: "message") ] manager.schedule() print("💬 iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "unknown".localized)") @@ -879,7 +882,7 @@ func waypointPacket (packet: MeshPacket, context: NSManagedObjectContext) { title: "New Waypoint Received", subtitle: "\(icon) \(waypoint.name ?? "Dropped Pin")", content: "\(waypoint.longDescription ?? "\(latitude), \(longitude)")", - target: "waypoint" + target: "map" ) ] manager.schedule() diff --git a/Meshtastic/MeshtasticAppDelegate.swift b/Meshtastic/MeshtasticAppDelegate.swift index c04a1b51..a86e9bf9 100644 --- a/Meshtastic/MeshtasticAppDelegate.swift +++ b/Meshtastic/MeshtasticAppDelegate.swift @@ -22,16 +22,17 @@ class MeshtasticAppDelegate: NSObject, UIApplicationDelegate, UNUserNotification func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { let userInfo = response.notification.request.content.userInfo - if let targetValue = userInfo["target"] as? String, targetValue == "waypoint" - { - openWaypoint() + let targetValue = userInfo["target"] as? String + if targetValue == "map" { + AppState.shared.tabSelection = Tab.map } - + else if targetValue == "message" { + AppState.shared.tabSelection = Tab.messages + } + else if targetValue == "node" { + AppState.shared.tabSelection = Tab.nodes + } + completionHandler() } - - private func openWaypoint() - { - AppState.shared.tabSelection = Tab.map - } } From c15b1b9454a3f461b0dfd42111c8c1e0a618a4f9 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 22 Aug 2023 11:15:14 -0700 Subject: [PATCH 25/40] Bump version, set minimum version to 16.2 --- Meshtastic.xcodeproj/project.pbxproj | 18 ++++--- Meshtastic/Helpers/MeshPackets.swift | 2 - Meshtastic/Views/Bluetooth/Connect.swift | 68 +++++++++++------------- 3 files changed, 41 insertions(+), 47 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index bf5f71ba..307fa716 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1332,12 +1332,12 @@ INFOPLIST_FILE = Meshtastic/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Meshtastic; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; - IPHONEOS_DEPLOYMENT_TARGET = 16.1; + IPHONEOS_DEPLOYMENT_TARGET = 16.2; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.2.1; + MARKETING_VERSION = 2.2.2; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1366,12 +1366,12 @@ INFOPLIST_FILE = Meshtastic/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Meshtastic; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; - IPHONEOS_DEPLOYMENT_TARGET = 16.1; + IPHONEOS_DEPLOYMENT_TARGET = 16.2; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.2.1; + MARKETING_VERSION = 2.2.2; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1391,7 +1391,7 @@ CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = GCH7VS5Y9R; INFOPLIST_FILE = MeshtasticTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.2; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1416,7 +1416,7 @@ CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = GCH7VS5Y9R; INFOPLIST_FILE = MeshtasticTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.2; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1437,6 +1437,7 @@ CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = GCH7VS5Y9R; INFOPLIST_FILE = MeshtasticUITests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 16.2; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1457,6 +1458,7 @@ CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = GCH7VS5Y9R; INFOPLIST_FILE = MeshtasticUITests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 16.2; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1484,7 +1486,7 @@ INFOPLIST_FILE = Widgets/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Widgets; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.2; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1515,7 +1517,7 @@ INFOPLIST_FILE = Widgets/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Widgets; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.2; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index c3384193..7eb6daaf 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -701,7 +701,6 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage } // Update our live activity if there is one running, not available on mac iOS >= 16.2 #if !targetEnvironment(macCatalyst) - if #available(iOS 16.2, *) { let oneMinuteLater = Calendar.current.date(byAdding: .minute, value: (Int(1) ), to: Date())! let date = Date.now...oneMinuteLater @@ -717,7 +716,6 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage print("Updated live activity.") } } - } #endif } } catch { diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index 33725aca..c696ee8b 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -89,22 +89,20 @@ struct Connect: View { if node != nil { #if !targetEnvironment(macCatalyst) - if #available(iOS 16.2, *) { - Button { - if !liveActivityStarted { + Button { + if !liveActivityStarted { + #if canImport(ActivityKit) + print("Start live activity.") + startNodeActivity() + #endif + } else { #if canImport(ActivityKit) - print("Start live activity.") - startNodeActivity() - #endif - } else { - #if canImport(ActivityKit) - print("Stop live activity.") - endActivity() - #endif - } - } label: { - Label("mesh.live.activity", systemImage: liveActivityStarted ? "stop" : "play") + print("Stop live activity.") + endActivity() + #endif } + } label: { + Label("mesh.live.activity", systemImage: liveActivityStarted ? "stop" : "play") } #endif Text("Num: \(String(node!.num))") @@ -293,40 +291,36 @@ struct Connect: View { } #if canImport(ActivityKit) func startNodeActivity() { - if #available(iOS 16.2, *) { - liveActivityStarted = true - let timerSeconds = 60 - - let deviceMetrics = node?.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")) - let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity + liveActivityStarted = true + let timerSeconds = 60 + + let deviceMetrics = node?.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")) + let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity - let activityAttributes = MeshActivityAttributes(nodeNum: Int(node?.num ?? 0), name: node?.user?.longName ?? "unknown") + let activityAttributes = MeshActivityAttributes(nodeNum: Int(node?.num ?? 0), name: node?.user?.longName ?? "unknown") - let future = Date(timeIntervalSinceNow: Double(timerSeconds)) + let future = Date(timeIntervalSinceNow: Double(timerSeconds)) - let initialContentState = MeshActivityAttributes.ContentState(timerRange: Date.now...future, connected: true, channelUtilization: mostRecent?.channelUtilization ?? 0.0, airtime: mostRecent?.airUtilTx ?? 0.0, batteryLevel: UInt32(mostRecent?.batteryLevel ?? 0)) + let initialContentState = MeshActivityAttributes.ContentState(timerRange: Date.now...future, connected: true, channelUtilization: mostRecent?.channelUtilization ?? 0.0, airtime: mostRecent?.airUtilTx ?? 0.0, batteryLevel: UInt32(mostRecent?.batteryLevel ?? 0)) - let activityContent = ActivityContent(state: initialContentState, staleDate: Calendar.current.date(byAdding: .minute, value: 2, to: Date())!) + let activityContent = ActivityContent(state: initialContentState, staleDate: Calendar.current.date(byAdding: .minute, value: 2, to: Date())!) - do { - let myActivity = try Activity.request(attributes: activityAttributes, content: activityContent, - pushType: nil) - print(" Requested MyActivity live activity. ID: \(myActivity.id)") - } catch let error { - print("Error requesting live activity: \(error.localizedDescription)") - } + do { + let myActivity = try Activity.request(attributes: activityAttributes, content: activityContent, + pushType: nil) + print(" Requested MyActivity live activity. ID: \(myActivity.id)") + } catch let error { + print("Error requesting live activity: \(error.localizedDescription)") } } func endActivity() { liveActivityStarted = false Task { - if #available(iOS 16.2, *) { - for activity in Activity.activities { - // Check if this is the activity associated with this order. - if activity.attributes.nodeNum == node?.num ?? 0 { - await activity.end(nil, dismissalPolicy: .immediate) - } + for activity in Activity.activities { + // Check if this is the activity associated with this order. + if activity.attributes.nodeNum == node?.num ?? 0 { + await activity.end(nil, dismissalPolicy: .immediate) } } } From 9c8cc6c2c74159a5f49b95ff6d73119df9c16883 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 22 Aug 2023 13:22:01 -0500 Subject: [PATCH 26/40] Add timestamps to detection sensor messages --- Meshtastic/Helpers/MeshPackets.swift | 1 + .../MeshtasticDataModelV16.xcdatamodel/contents | 1 + Meshtastic/Views/Messages/ChannelMessageList.swift | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index f025093e..26c21594 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -746,6 +746,7 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM let newMessage = MessageEntity(context: context) newMessage.messageId = Int64(packet.id) newMessage.messageTimestamp = Int32(bitPattern: packet.rxTime) + newMessage.receivedTimestamp = Int32(Date().timeIntervalSince1970) newMessage.receivedACK = false newMessage.snr = packet.rxSnr newMessage.rssi = packet.rxRssi diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV16.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV16.xcdatamodel/contents index 1e80b42a..b4a50f7d 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV16.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV16.xcdatamodel/contents @@ -144,6 +144,7 @@ + diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index 83e30709..ba8ae5af 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -197,6 +197,10 @@ struct ChannelMessageList: View { let ackErrorVal = RoutingError(rawValue: Int(message.ackError)) Text("\(ackErrorVal?.display ?? "Empty Ack Error")").fixedSize(horizontal: false, vertical: true) .font(.caption2).foregroundColor(.red) + } else if isDetectionSensorMessage { + let timeStamp = message.messageTimestamp <= 0 ? message.receivedTimestamp : message.messageTimestamp + let messageDate = Date(timeIntervalSince1970: TimeInterval(timeStamp)) + Text(" \(messageDate.formattedDate(format: dateFormatString))").font(.caption2).foregroundColor(.gray) } } } From 94b98bf0f32a2df7c4366f06c7cf6236dde9daff Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 22 Aug 2023 19:03:44 -0700 Subject: [PATCH 27/40] Update target --- Meshtastic.xcodeproj/project.pbxproj | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 307fa716..59bb01ca 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1246,7 +1246,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.2; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -1302,7 +1302,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.2; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -1479,9 +1479,9 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = Widgets/WidgetsExtension.entitlements; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = GCH7VS5Y9R; + DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Widgets/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Widgets; @@ -1495,6 +1495,7 @@ MARKETING_VERSION = 2.2.1; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; SWIFT_EMIT_LOC_STRINGS = YES; @@ -1510,9 +1511,9 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = Widgets/WidgetsExtension.entitlements; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = GCH7VS5Y9R; + DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Widgets/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Widgets; @@ -1526,6 +1527,7 @@ MARKETING_VERSION = 2.2.1; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; SWIFT_EMIT_LOC_STRINGS = YES; From 4b77796e7f57233b268abddde7e98eb7ac87461f Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 22 Aug 2023 20:19:20 -0700 Subject: [PATCH 28/40] Update project.pbxproj Bump widgets version --- Meshtastic.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 59bb01ca..ea462c85 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1492,7 +1492,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.1; + MARKETING_VERSION = 2.2.2; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1524,7 +1524,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.1; + MARKETING_VERSION = 2.2.2; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; From 3275bbf348e59a2bf1114fd8fb705f7441e6bb9c Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 23 Aug 2023 07:03:05 -0500 Subject: [PATCH 29/40] Added initial detection sensor log --- Meshtastic.xcodeproj/project.pbxproj | 8 + Meshtastic/Export/WriteCsvFile.swift | 15 ++ .../Persistence/MessageEntityExtension.swift | 21 +++ Meshtastic/Persistence/QueryCoreData.swift | 20 +++ .../Views/Helpers/Node/NodeInfoView.swift | 13 ++ .../Views/Messages/ChannelMessageList.swift | 3 +- .../Views/Nodes/DetectionSensorLog.swift | 157 ++++++++++++++++++ en.lproj/Localizable.strings | 1 + unthebenternify.sh | 4 + 9 files changed, 240 insertions(+), 2 deletions(-) create mode 100644 Meshtastic/Persistence/MessageEntityExtension.swift create mode 100644 Meshtastic/Views/Nodes/DetectionSensorLog.swift create mode 100755 unthebenternify.sh diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 307fa716..8c91ecab 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -8,6 +8,8 @@ /* Begin PBXBuildFile section */ 6DA39D8E2A92DC52007E311C /* MeshtasticAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */; }; + 6DEDA55A2A957B8E00321D2E /* DetectionSensorLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */; }; + 6DEDA55C2A9592F900321D2E /* MessageEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */; }; C9697F9D279336B700250207 /* LocalMBTileOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */; }; C9697FA527933B8C00250207 /* SQLite in Frameworks */ = {isa = PBXBuildFile; productRef = C9697FA427933B8C00250207 /* SQLite */; }; DD0D3D222A55CEB10066DB71 /* CocoaMQTT in Frameworks */ = {isa = PBXBuildFile; productRef = DD0D3D212A55CEB10066DB71 /* CocoaMQTT */; }; @@ -197,6 +199,8 @@ /* Begin PBXFileReference section */ 6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticAppDelegate.swift; sourceTree = ""; }; + 6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectionSensorLog.swift; sourceTree = ""; }; + 6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageEntityExtension.swift; sourceTree = ""; }; A65FA974296876BF00A97686 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMBTileOverlay.swift; sourceTree = ""; }; DD0E9C222A30CE3A00580CBB /* MeshtasticDataModelV14.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV14.xcdatamodel; sourceTree = ""; }; @@ -433,6 +437,7 @@ DD90860D26F69BAE00DC5189 /* NodeMap.swift */, DD73FD1028750779000852D6 /* PositionLog.swift */, DD14E72D2A82A614006E39BC /* RemoteHardware.swift */, + 6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */, ); path = Nodes; sourceTree = ""; @@ -746,6 +751,7 @@ DD964FC52975DBFD007C176F /* QueryCoreData.swift */, DD3CC6C128EB9D4900FA9159 /* UpdateCoreData.swift */, DD964FC1297272AE007C176F /* WaypointEntityExtension.swift */, + 6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */, ); path = Persistence; sourceTree = ""; @@ -1009,6 +1015,7 @@ DD3501892852FC3B000FC853 /* Settings.swift in Sources */, DDDB443629F6287000EE2349 /* MapButtons.swift in Sources */, DD5D0A9C2931B9F200F7EA61 /* EthernetModes.swift in Sources */, + 6DEDA55A2A957B8E00321D2E /* DetectionSensorLog.swift in Sources */, DD5E5203298EE33B00D21B61 /* config.pb.swift in Sources */, DD798B072915928D005217CD /* ChannelMessageList.swift in Sources */, DDA6B2EB28420A7B003E8C16 /* NodeAnnotation.swift in Sources */, @@ -1039,6 +1046,7 @@ DD4A911E2708C65400501B7E /* AppSettings.swift in Sources */, DD5E5209298EE33B00D21B61 /* module_config.pb.swift in Sources */, DD2160AF28C5552500C17253 /* MQTTConfig.swift in Sources */, + 6DEDA55C2A9592F900321D2E /* MessageEntityExtension.swift in Sources */, DDDB444229F8A88700EE2349 /* Double.swift in Sources */, DD5E520F298EE33B00D21B61 /* cannedmessages.pb.swift in Sources */, DDB75A232A13CDA9006ED576 /* BatteryLevelCompact.swift in Sources */, diff --git a/Meshtastic/Export/WriteCsvFile.swift b/Meshtastic/Export/WriteCsvFile.swift index db8ffdbe..c246fa40 100644 --- a/Meshtastic/Export/WriteCsvFile.swift +++ b/Meshtastic/Export/WriteCsvFile.swift @@ -53,6 +53,21 @@ func telemetryToCsvFile(telemetry: [TelemetryEntity], metricsType: Int) -> Strin return csvString } +func detectionsToCsv(detections: [MessageEntity]) -> String { + var csvString: String = "" + let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current) + let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma").replacingOccurrences(of: ",", with: "") + // Create Header + csvString = "Detection event, \("timestamp".localized)" + for d in detections { + csvString += "\n" + csvString += d.messagePayload ?? "Detection" + csvString += ", " + csvString += d.timestamp.formattedDate(format: dateFormatString).localized + } + return csvString +} + func positionToCsvFile(positions: [PositionEntity]) -> String { var csvString: String = "" let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current) diff --git a/Meshtastic/Persistence/MessageEntityExtension.swift b/Meshtastic/Persistence/MessageEntityExtension.swift new file mode 100644 index 00000000..400b2f97 --- /dev/null +++ b/Meshtastic/Persistence/MessageEntityExtension.swift @@ -0,0 +1,21 @@ +// +// MessageEntityExtension.swift +// Meshtastic +// +// Created by Ben on 8/22/23. +// + +import Foundation + +import CoreData +import CoreLocation +import MapKit +import SwiftUI + +extension MessageEntity { + + var timestamp: Date { + let time = messageTimestamp <= 0 ? receivedTimestamp : messageTimestamp + return Date(timeIntervalSince1970: TimeInterval(time)) + } +} diff --git a/Meshtastic/Persistence/QueryCoreData.swift b/Meshtastic/Persistence/QueryCoreData.swift index cf1a3190..d49c8176 100644 --- a/Meshtastic/Persistence/QueryCoreData.swift +++ b/Meshtastic/Persistence/QueryCoreData.swift @@ -60,3 +60,23 @@ public func getWaypoint(id: Int64, context: NSManagedObjectContext) -> WaypointE } return WaypointEntity(context: context) } + + +public func getDetectionSensorMessages(nodeNum: Int64?, context: NSManagedObjectContext) -> [MessageEntity] { + + let fetchDetectionMessagesPredicate: NSFetchRequest = NSFetchRequest.init(entityName: "MessageEntity") + fetchDetectionMessagesPredicate.predicate = NSPredicate(format: "portNum == %d", Int32(PortNum.detectionSensorApp.rawValue)) + + do { + let fetched = try context.fetch(fetchDetectionMessagesPredicate) as? [MessageEntity] ?? [] + if nodeNum == nil { + return fetched.reversed() + } + return fetched.filter { message in + return message.fromUser?.num == nodeNum! + }.reversed() + } + catch { + return [] + } +} diff --git a/Meshtastic/Views/Helpers/Node/NodeInfoView.swift b/Meshtastic/Views/Helpers/Node/NodeInfoView.swift index 528aa263..c9570bd4 100644 --- a/Meshtastic/Views/Helpers/Node/NodeInfoView.swift +++ b/Meshtastic/Views/Helpers/Node/NodeInfoView.swift @@ -243,6 +243,19 @@ struct NodeInfoView: View { } Divider() } + NavigationLink { + DetectionSensorLog(node: node) + } label: { + + Image(systemName: "sensor") + .symbolRenderingMode(.hierarchical) + .font(.title) + + Text("Detection Sensor Log") + .font(.title3) + } + .fixedSize(horizontal: false, vertical: true) + Divider() } } } diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index ba8ae5af..e82b2138 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -198,8 +198,7 @@ struct ChannelMessageList: View { Text("\(ackErrorVal?.display ?? "Empty Ack Error")").fixedSize(horizontal: false, vertical: true) .font(.caption2).foregroundColor(.red) } else if isDetectionSensorMessage { - let timeStamp = message.messageTimestamp <= 0 ? message.receivedTimestamp : message.messageTimestamp - let messageDate = Date(timeIntervalSince1970: TimeInterval(timeStamp)) + let messageDate = message.timestamp Text(" \(messageDate.formattedDate(format: dateFormatString))").font(.caption2).foregroundColor(.gray) } } diff --git a/Meshtastic/Views/Nodes/DetectionSensorLog.swift b/Meshtastic/Views/Nodes/DetectionSensorLog.swift new file mode 100644 index 00000000..f8dd841d --- /dev/null +++ b/Meshtastic/Views/Nodes/DetectionSensorLog.swift @@ -0,0 +1,157 @@ +// +// DetectionSensorLog.swift +// Meshtastic +// +// Created by Ben on 8/22/23. +// + +import SwiftUI +import Charts + +struct DetectionSensorLog: View { + + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + + @State private var isPresentingClearLogConfirm: Bool = false + @State var isExporting = false + @State var exportString = "" + + var node: NodeInfoEntity + + var body: some View { + + let oneDayAgo = Calendar.current.date(byAdding: .day, value: -1, to: Date()) + let detections = getDetectionSensorMessages(nodeNum: node.num, context: context) + let chartData = detections + .filter { $0.timestamp >= oneDayAgo! } + .sorted { $0.timestamp < $1.timestamp } + + NavigationStack { + + if chartData.count > 0 { + GroupBox(label: Label("\(detections.count) Total Detection Events", systemImage: "sensor")) { + + Chart { + ForEach(chartData, id: \.self) { point in + Plot { + BarMark( + x: .value("x", point.timestamp), + y: .value("y", 1) + ) + } + .accessibilityLabel("Bar Series") + .accessibilityValue("X: \(point.timestamp), Y: \(1)") + .interpolationMethod(.cardinal) + .foregroundStyle( + .linearGradient( + colors: [.green, .yellow, .orange, .red], + startPoint: .bottom, + endPoint: .top + ) + ) + .alignsMarkStylesWithPlotArea() + } + } + .chartXAxis(content: { + AxisMarks(position: .top) +// AxisMarks(position: .top, values: .stride(by: .hour)) { date in +// AxisValueLabel(format: .dateTime.hour()) +// } + }) + .chartXAxis(.automatic) + .chartYScale(domain: 0...20) + .chartForegroundStyleScale([ + "Detection events" : .green, + ]) + .chartLegend(position: .automatic, alignment: .bottom) + } + .frame(minHeight: 250) + } + let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current) + let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma").replacingOccurrences(of: ",", with: "") + if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { + + // Add a table for mac and ipad + Table(detections) { + TableColumn("Detection event") { d in + Text(d.messagePayload ?? "Detected") + } + + TableColumn("timestamp") { d in + Text(d.timestamp.formattedDate(format: dateFormatString)) + } + .width(min: 180) + } + } else { + ScrollView { + let columns = [ + GridItem(), + GridItem() + ] + LazyVGrid(columns: columns, alignment: .leading, spacing: 1) { + GridRow { + Text("Detection") + .font(.caption) + .fontWeight(.bold) + Text("timestamp") + .font(.caption) + .fontWeight(.bold) + } + ForEach(detections) { d in + GridRow { + Text(d.messagePayload ?? "Detected") + Text(d.timestamp.formattedDate(format: dateFormatString)) + .font(.caption) + } + } + } + .padding(.leading, 15) + .padding(.trailing, 5) + } + } + } + HStack { + Button { + exportString = detectionsToCsv(detections: chartData) + isExporting = true + } label: { + Label("save", systemImage: "square.and.arrow.down") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding(.bottom) + .padding(.trailing) + } + .navigationTitle("detection.sensor.log") + .navigationBarTitleDisplayMode(.inline) + .navigationBarItems(trailing: + ZStack { + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") + }) + .onAppear { + self.bleManager.context = context + } + .fileExporter( + isPresented: $isExporting, + document: CsvDocument(emptyCsv: exportString), + contentType: .commaSeparatedText, + defaultFilename: String("\(node.user?.longName ?? "Node") \("detection.sensor.log".localized)"), + onCompletion: { result in + if case .success = result { + print("Detections metrics log download succeeded.") + self.isExporting = false + } else { + print("Detections log download failed: \(result).") + } + } + ) + } +} +// +//struct DetectionSensorLog_Previews: PreviewProvider { +// static var previews: some View { +// DetectionSensorLog() +// } +//} diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index b6166ab2..18db64fc 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -59,6 +59,7 @@ "delete"="Delete"; "detection.sensor"="Detection Sensor"; "detection.sensor.config"="Detection Sensor Config"; +"detection.sensor.log"="Detection Sensor Log"; "device"="Device"; "device.config"="Device Config"; "device.metrics.delete"="Delete all device metrics?"; diff --git a/unthebenternify.sh b/unthebenternify.sh new file mode 100755 index 00000000..60dbd6bb --- /dev/null +++ b/unthebenternify.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +sed -i '' -e 's/6YF6QJH524/GCH7VS5Y9R/g' ./Meshtastic.xcodeproj/project.pbxproj +sed -i '' -e 's/thebentern.Meshtastic/gvh.Meshtastic/g' ./Meshtastic.xcodeproj/project.pbxproj From b8a1bc7b57e3d3ab408255b4c2e3062bb729232a Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 23 Aug 2023 08:17:44 -0700 Subject: [PATCH 30/40] Automatic code signing --- Meshtastic.xcodeproj/project.pbxproj | 10 ++++++---- Widgets/WidgetsBundle.swift | 1 - 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 59bb01ca..2ad88913 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1479,9 +1479,10 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = Widgets/WidgetsExtension.entitlements; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = GCH7VS5Y9R; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Widgets/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Widgets; @@ -1511,9 +1512,10 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = Widgets/WidgetsExtension.entitlements; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = GCH7VS5Y9R; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Widgets/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Widgets; diff --git a/Widgets/WidgetsBundle.swift b/Widgets/WidgetsBundle.swift index 83877dff..15d53f4e 100644 --- a/Widgets/WidgetsBundle.swift +++ b/Widgets/WidgetsBundle.swift @@ -8,7 +8,6 @@ import WidgetKit import SwiftUI -@available(iOSApplicationExtension 16.2, *) @main struct WidgetsBundle: WidgetBundle { var body: some Widget { From 37e21a39390652909a50c47d90a1f1e25c56fbe9 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 24 Aug 2023 22:25:06 -0700 Subject: [PATCH 31/40] Update protos --- Meshtastic/Protobufs/meshtastic/mesh.pb.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Meshtastic/Protobufs/meshtastic/mesh.pb.swift b/Meshtastic/Protobufs/meshtastic/mesh.pb.swift index 959ef2c2..9467436f 100644 --- a/Meshtastic/Protobufs/meshtastic/mesh.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/mesh.pb.swift @@ -204,6 +204,10 @@ enum HardwareModel: SwiftProtobuf.Enum { /// Bobricius Picomputer with ESP32-S3 CPU, Keyboard and IPS display case picomputerS3 // = 52 + /// + /// Heltec HT-CT62 with ESP32-C3 CPU and SX1262 LoRa + case heltecHt62 // = 53 + /// /// ------------------------------------------------------------------------------------------------------------------------------------------ /// Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. @@ -260,6 +264,7 @@ enum HardwareModel: SwiftProtobuf.Enum { case 50: self = .tDeck case 51: self = .tWatchS3 case 52: self = .picomputerS3 + case 53: self = .heltecHt62 case 255: self = .privateHw default: self = .UNRECOGNIZED(rawValue) } @@ -310,6 +315,7 @@ enum HardwareModel: SwiftProtobuf.Enum { case .tDeck: return 50 case .tWatchS3: return 51 case .picomputerS3: return 52 + case .heltecHt62: return 53 case .privateHw: return 255 case .UNRECOGNIZED(let i): return i } @@ -365,6 +371,7 @@ extension HardwareModel: CaseIterable { .tDeck, .tWatchS3, .picomputerS3, + .heltecHt62, .privateHw, ] } @@ -2527,6 +2534,7 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding { 50: .same(proto: "T_DECK"), 51: .same(proto: "T_WATCH_S3"), 52: .same(proto: "PICOMPUTER_S3"), + 53: .same(proto: "HELTEC_HT62"), 255: .same(proto: "PRIVATE_HW"), ] } From 7ab8cba73ac9e015d4deeb56cb67f713a5158c3f Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 26 Aug 2023 18:21:33 -0700 Subject: [PATCH 32/40] Bump sqlite version --- .../xcshareddata/swiftpm/Package.resolved | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Meshtastic.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Meshtastic.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 4a3e14db..85e8785d 100644 --- a/Meshtastic.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Meshtastic.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/stephencelis/SQLite.swift.git", "state" : { - "revision" : "60a65015f6402b7c34b9a924f755ca0a73afeeaa", - "version" : "0.13.1" + "revision" : "7a2e3cd27de56f6d396e84f63beefd0267b55ccb", + "version" : "0.14.1" } }, { @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-protobuf.git", "state" : { - "revision" : "e1499bc69b9040b29184f7f2996f7bab467c1639", - "version" : "1.19.0" + "revision" : "ce20dc083ee485524b802669890291c0d8090170", + "version" : "1.22.1" } } ], From e1109d32824c4b5aadb35be9b29cba1d19fa8592 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 26 Aug 2023 20:45:15 -0700 Subject: [PATCH 33/40] Store and forward --- Meshtastic.xcodeproj/project.pbxproj | 10 +- Meshtastic/Helpers/BLEManager.swift | 88 +++-- .../Meshtastic.xcdatamodeld/.xccurrentversion | 2 +- .../contents | 355 ++++++++++++++++++ Meshtastic/Persistence/UpdateCoreData.swift | 172 ++++++--- Meshtastic/Views/ContentView.swift | 1 + .../Views/Nodes/EnvironmentMetricsLog.swift | 2 +- Meshtastic/Views/Nodes/NodeDetail.swift | 1 - Meshtastic/Views/Nodes/PositionLog.swift | 2 +- Meshtastic/Views/Settings/About.swift | 2 +- .../Settings/Config/Module/StoreForward.swift | 185 +++++++++ Meshtastic/Views/Settings/Settings.swift | 9 + .../Contents.json | 0 .../Mesh_Logo_Black.svg | 0 .../Mesh_Logo_Black_Large.svg | 0 .../Mesh_Logo_Black_Small.svg | 0 .../Contents.json | 0 .../Mesh_Logo_White.svg | 0 .../Mesh_Logo_White_Large.svg | 0 .../Mesh_Logo_White_Small.svg | 0 Widgets/WidgetsLiveActivity.swift | 6 +- en.lproj/Localizable.strings | 3 + swiftlint.geojson | 6 + 23 files changed, 742 insertions(+), 102 deletions(-) create mode 100644 Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV17.xcdatamodel/contents create mode 100644 Meshtastic/Views/Settings/Config/Module/StoreForward.swift rename Widgets/Assets.xcassets/{logo-black.imageset => m-logo-black.imageset}/Contents.json (100%) rename Widgets/Assets.xcassets/{logo-black.imageset => m-logo-black.imageset}/Mesh_Logo_Black.svg (100%) rename Widgets/Assets.xcassets/{logo-black.imageset => m-logo-black.imageset}/Mesh_Logo_Black_Large.svg (100%) rename Widgets/Assets.xcassets/{logo-black.imageset => m-logo-black.imageset}/Mesh_Logo_Black_Small.svg (100%) rename Widgets/Assets.xcassets/{logo-white.imageset => m-logo-white.imageset}/Contents.json (100%) rename Widgets/Assets.xcassets/{logo-white.imageset => m-logo-white.imageset}/Mesh_Logo_White.svg (100%) rename Widgets/Assets.xcassets/{logo-white.imageset => m-logo-white.imageset}/Mesh_Logo_White_Large.svg (100%) rename Widgets/Assets.xcassets/{logo-white.imageset => m-logo-white.imageset}/Mesh_Logo_White_Small.svg (100%) create mode 100644 swiftlint.geojson diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 5fe23104..772276dd 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -155,6 +155,7 @@ DDDE5A1429AFEAB900490C6C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDDE5A1229AFEAB900490C6C /* Assets.xcassets */; }; DDDEE5E129DA3E1100A8E078 /* NodeInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDEE5E029DA3E1100A8E078 /* NodeInfoView.swift */; }; DDE0F7C5295F77B700B8AAB3 /* AppSettingsEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */; }; + DDF6B2482A9AEBF500BA6931 /* StoreForward.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF6B2472A9AEBF500BA6931 /* StoreForward.swift */; }; DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF924C926FBB953009FE055 /* ConnectedDevice.swift */; }; DDFEB3BB29900C1200EE7472 /* CurrentConditionsCompact.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */; }; /* End PBXBuildFile section */ @@ -366,6 +367,8 @@ DDDEE5E229DBE43E00A8E078 /* MeshtasticDataModelV11.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV11.xcdatamodel; sourceTree = ""; }; DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsEnums.swift; sourceTree = ""; }; DDEE03EC29544A1000FCAD57 /* MeshtasticDataModelV4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV4.xcdatamodel; sourceTree = ""; }; + DDF6B2462A9AEB9E00BA6931 /* MeshtasticDataModelV17.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV17.xcdatamodel; sourceTree = ""; }; + DDF6B2472A9AEBF500BA6931 /* StoreForward.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreForward.swift; sourceTree = ""; }; DDF924C926FBB953009FE055 /* ConnectedDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedDevice.swift; sourceTree = ""; }; DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentConditionsCompact.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -521,13 +524,14 @@ isa = PBXGroup; children = ( DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */, + DDC4C9FE2A8D982900CE201C /* DetectionSensorConfig.swift */, DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */, DD2160AE28C5552500C17253 /* MQTTConfig.swift */, DD41582928585C32009B0E59 /* RangeTestConfig.swift */, DDC94FCD29CF55310082EA6E /* RtttlConfig.swift */, DD6193782863875F00E59241 /* SerialConfig.swift */, + DDF6B2472A9AEBF500BA6931 /* StoreForward.swift */, DD415827285859C4009B0E59 /* TelemetryConfig.swift */, - DDC4C9FE2A8D982900CE201C /* DetectionSensorConfig.swift */, ); path = Module; sourceTree = ""; @@ -1128,6 +1132,7 @@ DDC4C9FF2A8D982900CE201C /* DetectionSensorConfig.swift in Sources */, DD5E5210298EE33B00D21B61 /* telemetry.pb.swift in Sources */, DD5E5205298EE33B00D21B61 /* mesh.pb.swift in Sources */, + DDF6B2482A9AEBF500BA6931 /* StoreForward.swift in Sources */, DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */, DD41582A28585C32009B0E59 /* RangeTestConfig.swift in Sources */, DD1925B728CDA5A400720036 /* CannedMessagesConfigEnums.swift in Sources */, @@ -1645,6 +1650,7 @@ DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + DDF6B2462A9AEB9E00BA6931 /* MeshtasticDataModelV17.xcdatamodel */, DDC4CA012A8DAA3800CE201C /* MeshtasticDataModelV16.xcdatamodel */, DD14E72C2A80738F006E39BC /* MeshtasticDataModelV15.xcdatamodel */, DD0E9C222A30CE3A00580CBB /* MeshtasticDataModelV14.xcdatamodel */, @@ -1662,7 +1668,7 @@ DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */, DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */, ); - currentVersion = DDC4CA012A8DAA3800CE201C /* MeshtasticDataModelV16.xcdatamodel */; + currentVersion = DDF6B2462A9AEB9E00BA6931 /* MeshtasticDataModelV17.xcdatamodel */; name = Meshtastic.xcdatamodeld; path = Meshtastic/Meshtastic.xcdatamodeld; sourceTree = ""; diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 268b64af..ade1b866 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -1384,6 +1384,32 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate return 0 } + public func saveDetectionSensorModuleConfig(config: ModuleConfig.DetectionSensorConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { + + var adminPacket = AdminMessage() + adminPacket.setModuleConfig.detectionSensor = config + + var meshPacket: MeshPacket = MeshPacket() + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { var adminPacket = AdminMessage() @@ -1402,7 +1428,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage - let messageDescription = "Saved External Notification Module Config for \(toUser.longName ?? "unknown".localized)" + let messageDescription = "🛟 Saved External Notification Module Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { upsertExternalNotificationModuleConfigPacket(config: config, nodeNum: toUser.num, context: context!) return Int64(meshPacket.id) @@ -1427,7 +1453,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage - let messageDescription = "Saved RTTTL Ringtone Config for \(toUser.longName ?? "unknown".localized)" + let messageDescription = "🛟 Saved RTTTL Ringtone Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { upsertRtttlConfigPacket(ringtone: ringtone, nodeNum: toUser.num, context: context!) @@ -1456,7 +1482,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.decoded = dataMessage - let messageDescription = "Saved WiFi Config for \(toUser.longName ?? "unknown".localized)" + let messageDescription = "🛟 Saved MQTT Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { upsertMqttModuleConfigPacket(config: config, nodeNum: toUser.num, context: context!) return Int64(meshPacket.id) @@ -1481,7 +1507,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage - let messageDescription = "Saved Range Test Module Config for \(toUser.longName ?? "unknown".localized)" + let messageDescription = "🛟 Saved Range Test Module Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { upsertRangeTestModuleConfigPacket(config: config, nodeNum: toUser.num, context: context!) @@ -1509,7 +1535,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage - let messageDescription = "Saved Serial Module Config for \(toUser.longName ?? "unknown".localized)" + let messageDescription = "🛟 Saved Serial Module Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { upsertSerialModuleConfigPacket(config: config, nodeNum: toUser.num, context: context!) return Int64(meshPacket.id) @@ -1517,6 +1543,32 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate return 0 } + public func saveStoreForwardModuleConfig(config: ModuleConfig.StoreForwardConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { + + var adminPacket = AdminMessage() + adminPacket.setModuleConfig.storeForward = config + + var meshPacket: MeshPacket = MeshPacket() + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { var adminPacket = AdminMessage() @@ -1543,32 +1595,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate return 0 } - public func saveDetectionSensorModuleConfig(config: ModuleConfig.DetectionSensorConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { - - var adminPacket = AdminMessage() - adminPacket.setModuleConfig.detectionSensor = config - - var meshPacket: MeshPacket = MeshPacket() - meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { var adminPacket = AdminMessage() diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index 7ccbfdf1..6b72864b 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModelV16.xcdatamodel + MeshtasticDataModelV17.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV17.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV17.xcdatamodel/contents new file mode 100644 index 00000000..2f6acb17 --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV17.xcdatamodel/contents @@ -0,0 +1,355 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 5270461f..169f004d 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -650,6 +650,67 @@ func upsertCannedMessagesModuleConfigPacket(config: Meshtastic.ModuleConfig.Cann } } +func upsertDetectionSensorModuleConfigPacket(config: Meshtastic.ModuleConfig.DetectionSensorConfig, nodeNum: Int64, context: NSManagedObjectContext) { + + let logString = String.localizedStringWithFormat("mesh.log.detectionsensor.config %@".localized, String(nodeNum)) + MeshLogger.log("📈 \(logString)") + + let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + + guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else { + return + } + // Found a node, save Detection Sensor Config + if !fetchedNode.isEmpty { + + if fetchedNode[0].detectionSensorConfig == nil { + + let newConfig = DetectionSensorConfigEntity(context: context) + newConfig.enabled = config.enabled + newConfig.sendBell = config.sendBell + newConfig.name = config.name + + newConfig.monitorPin = Int32(config.monitorPin) + newConfig.detectionTriggeredHigh = config.detectionTriggeredHigh + newConfig.usePullup = config.usePullup + newConfig.minimumBroadcastSecs = Int32(config.minimumBroadcastSecs) + newConfig.stateBroadcastSecs = Int32(config.stateBroadcastSecs) + fetchedNode[0].detectionSensorConfig = newConfig + + } else { + fetchedNode[0].detectionSensorConfig?.enabled = config.enabled + fetchedNode[0].detectionSensorConfig?.sendBell = config.sendBell + fetchedNode[0].detectionSensorConfig?.name = config.name + fetchedNode[0].detectionSensorConfig?.monitorPin = Int32(config.monitorPin) + fetchedNode[0].detectionSensorConfig?.usePullup = config.usePullup + fetchedNode[0].detectionSensorConfig?.detectionTriggeredHigh = config.detectionTriggeredHigh + fetchedNode[0].detectionSensorConfig?.minimumBroadcastSecs = Int32(config.minimumBroadcastSecs) + fetchedNode[0].detectionSensorConfig?.stateBroadcastSecs = Int32(config.stateBroadcastSecs) + } + + do { + try context.save() + print("💾 Updated Detection Sensor Module Config for node number: \(String(nodeNum))") + + } catch { + context.rollback() + let nsError = error as NSError + print("💥 Error Updating Core Data DetectionSensorConfigEntity: \(nsError)") + } + + } else { + print("💥 No Nodes found in local database matching node number \(nodeNum) unable to save Detection Sensor Module Config") + } + + } catch { + let nsError = error as NSError + print("💥 Fetching node for core data DetectionSensorConfigEntity failed: \(nsError)") + } +} + func upsertExternalNotificationModuleConfigPacket(config: Meshtastic.ModuleConfig.ExternalNotificationConfig, nodeNum: Int64, context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.externalnotification.config %@".localized, String(nodeNum)) @@ -919,6 +980,56 @@ func upsertSerialModuleConfigPacket(config: Meshtastic.ModuleConfig.SerialConfig } } +func upsertStoreForwardModuleConfigPacket(config: Meshtastic.ModuleConfig.StoreForwardConfig, nodeNum: Int64, context: NSManagedObjectContext) { + + let logString = String.localizedStringWithFormat("mesh.log.storeforward.config %@".localized, String(nodeNum)) + MeshLogger.log("📬 \(logString)") + + let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + + guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else { + return + } + // Found a node, save Store & Forward Sensor Config + if !fetchedNode.isEmpty { + + if fetchedNode[0].storeForwardConfig == nil { + + let newConfig = StoreForwardConfigEntity(context: context) + newConfig.enabled = config.enabled + newConfig.heartbeat = config.heartbeat + newConfig.records = Int32(config.records) + newConfig.historyReturnMax = Int32(config.historyReturnMax) + newConfig.historyReturnWindow = Int32(config.historyReturnWindow) + fetchedNode[0].storeForwardConfig = newConfig + + } else { + fetchedNode[0].storeForwardConfig?.enabled = config.enabled + fetchedNode[0].storeForwardConfig?.heartbeat = config.heartbeat + fetchedNode[0].storeForwardConfig?.records = Int32(config.records) + fetchedNode[0].storeForwardConfig?.historyReturnMax = Int32(config.historyReturnMax) + fetchedNode[0].storeForwardConfig?.historyReturnWindow = Int32(config.historyReturnWindow) + } + do { + try context.save() + print("💾 Updated Store & Forward Module Config for node number: \(String(nodeNum))") + } catch { + context.rollback() + let nsError = error as NSError + print("💥 Error Updating Core Data StoreForwardConfigEntity: \(nsError)") + } + } else { + print("💥 No Nodes found in local database matching node number \(nodeNum) unable to save Store & Forward Module Config") + } + } catch { + let nsError = error as NSError + print("💥 Fetching node for core data DetectionSensorConfigEntity failed: \(nsError)") + } +} + func upsertTelemetryModuleConfigPacket(config: Meshtastic.ModuleConfig.TelemetryConfig, nodeNum: Int64, context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.telemetry.config %@".localized, String(nodeNum)) @@ -972,64 +1083,3 @@ func upsertTelemetryModuleConfigPacket(config: Meshtastic.ModuleConfig.Telemetry print("💥 Fetching node for core data TelemetryConfigEntity failed: \(nsError)") } } - -func upsertDetectionSensorModuleConfigPacket(config: Meshtastic.ModuleConfig.DetectionSensorConfig, nodeNum: Int64, context: NSManagedObjectContext) { - - let logString = String.localizedStringWithFormat("mesh.log.detectionsensor.config %@".localized, String(nodeNum)) - MeshLogger.log("📈 \(logString)") - - let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") - fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) - - do { - - guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else { - return - } - // Found a node, save Detection Sensor Config - if !fetchedNode.isEmpty { - - if fetchedNode[0].detectionSensorConfig == nil { - - let newConfig = DetectionSensorConfigEntity(context: context) - newConfig.enabled = config.enabled - newConfig.sendBell = config.sendBell - newConfig.name = config.name - - newConfig.monitorPin = Int32(config.monitorPin) - newConfig.detectionTriggeredHigh = config.detectionTriggeredHigh - newConfig.usePullup = config.usePullup - newConfig.minimumBroadcastSecs = Int32(config.minimumBroadcastSecs) - newConfig.stateBroadcastSecs = Int32(config.stateBroadcastSecs) - fetchedNode[0].detectionSensorConfig = newConfig - - } else { - fetchedNode[0].detectionSensorConfig?.enabled = config.enabled - fetchedNode[0].detectionSensorConfig?.sendBell = config.sendBell - fetchedNode[0].detectionSensorConfig?.name = config.name - fetchedNode[0].detectionSensorConfig?.monitorPin = Int32(config.monitorPin) - fetchedNode[0].detectionSensorConfig?.usePullup = config.usePullup - fetchedNode[0].detectionSensorConfig?.detectionTriggeredHigh = config.detectionTriggeredHigh - fetchedNode[0].detectionSensorConfig?.minimumBroadcastSecs = Int32(config.minimumBroadcastSecs) - fetchedNode[0].detectionSensorConfig?.stateBroadcastSecs = Int32(config.stateBroadcastSecs) - } - - do { - try context.save() - print("💾 Updated Detection Sensor Module Config for node number: \(String(nodeNum))") - - } catch { - context.rollback() - let nsError = error as NSError - print("💥 Error Updating Core Data DetectionSensorConfigEntity: \(nsError)") - } - - } else { - print("💥 No Nodes found in local database matching node number \(nodeNum) unable to save Detection Sensor Module Config") - } - - } catch { - let nsError = error as NSError - print("💥 Fetching node for core data DetectionSensorConfigEntity failed: \(nsError)") - } -} diff --git a/Meshtastic/Views/ContentView.swift b/Meshtastic/Views/ContentView.swift index f630d195..32f783b3 100644 --- a/Meshtastic/Views/ContentView.swift +++ b/Meshtastic/Views/ContentView.swift @@ -33,6 +33,7 @@ struct ContentView: View { Settings() .tabItem { Label("settings", systemImage: "gear") + .font(.title) } .tag(Tab.settings) } diff --git a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift index dc490a84..f7930c44 100644 --- a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift +++ b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift @@ -29,7 +29,7 @@ struct EnvironmentMetricsLog: View { .sorted { $0.time! < $1.time! } let locale = NSLocale.current as NSLocale let localeUnit = locale.object(forKey: NSLocale.Key(rawValue: "kCFLocaleTemperatureUnitKey")) - var format: UnitTemperature = localeUnit as? String ?? "Celsius" == "Fahrenheit" ? .fahrenheit : .celsius + let format: UnitTemperature = localeUnit as? String ?? "Celsius" == "Fahrenheit" ? .fahrenheit : .celsius NavigationStack { diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 8e3ad3c5..1ffc9997 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -53,7 +53,6 @@ struct NodeDetail: View { var body: some View { - let hwModelString = node.user?.hwModel ?? "UNSET" let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) NavigationStack { GeometryReader { bounds in diff --git a/Meshtastic/Views/Nodes/PositionLog.swift b/Meshtastic/Views/Nodes/PositionLog.swift index 95df3f75..011c461d 100644 --- a/Meshtastic/Views/Nodes/PositionLog.swift +++ b/Meshtastic/Views/Nodes/PositionLog.swift @@ -32,7 +32,7 @@ struct PositionLog: View { let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma").replacingOccurrences(of: ",", with: "") if UIDevice.current.userInterfaceIdiom == .pad && !useGrid || UIDevice.current.userInterfaceIdiom == .mac { // Add a table for mac and ipad - var positions = node.positions?.reversed() as? [PositionEntity] ?? [] + let positions = node.positions?.reversed() as? [PositionEntity] ?? [] Table(positions) { TableColumn("Latitude") { position in diff --git a/Meshtastic/Views/Settings/About.swift b/Meshtastic/Views/Settings/About.swift index 8edb32a4..b395a9c0 100644 --- a/Meshtastic/Views/Settings/About.swift +++ b/Meshtastic/Views/Settings/About.swift @@ -36,7 +36,7 @@ struct AboutMeshtastic: View { } if locale.region?.identifier ?? "no locale" == "US" { Section(header: Text("Get Devices")) { - Link("Buy Complete Radios", destination: URL(string: "https://www.etsy.com/shop/GarthVH")!) + Link("Buy Complete Radios", destination: URL(string: "http://garthvh.com")!) .font(.title2) } } diff --git a/Meshtastic/Views/Settings/Config/Module/StoreForward.swift b/Meshtastic/Views/Settings/Config/Module/StoreForward.swift new file mode 100644 index 00000000..e2572a14 --- /dev/null +++ b/Meshtastic/Views/Settings/Config/Module/StoreForward.swift @@ -0,0 +1,185 @@ +// +// StoreForward.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 8/26/23. +// + +import SwiftUI + +struct StoreForwardConfig: View { + + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + @Environment(\.dismiss) private var goBack + var node: NodeInfoEntity? + @State private var isPresentingSaveConfirm: Bool = false + @State var hasChanges: Bool = false + /// Enable the Store and Forward Module + @State var enabled = false + /// Send a Heartbeat + @State var heartbeat: Bool = false + /// Number of Records + @State var records = 0 + /// Max number of history items to return + @State var historyReturnMax = 0 + /// Time window for history + @State var historyReturnWindow = 0 + + var body: some View { + + Form { + if node != nil && node?.metadata == nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 { + Text("There has been no response to a request for device metadata over the admin channel for this node.") + .font(.callout) + .foregroundColor(.orange) + + } else if node != nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 { + // Let users know what is going on if they are using remote admin and don't have the config yet + if node?.detectionSensorConfig == nil { + Text("Detection Sensor config data was requested over the admin channel but no response has been returned from the remote node. You can check the status of admin message requests in the admin message log.") + .font(.callout) + .foregroundColor(.orange) + } else { + Text("Remote administration for: \(node?.user?.longName ?? "Unknown")") + .font(.title3) + .onAppear { + setDetectionSensorValues() + } + } + } else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 { + Text("Configuration for: \(node?.user?.longName ?? "Unknown")") + .font(.title3) + } else { + Text("Please connect to a radio to configure settings.") + .font(.callout) + .foregroundColor(.orange) + } + Section(header: Text("options")) { + Toggle(isOn: $enabled) { + Label("enabled", systemImage: "envelope.arrow.triangle.branch") + } + Toggle(isOn: $heartbeat) { + Label("storeforward.heartbeat", systemImage: "waveform.path.ecg") + } + Picker("Number of records", selection: $records) { + Text("unset").tag(0) + Text("25").tag(25) + Text("50").tag(50) + Text("75").tag(75) + Text("100").tag(100) + } + .pickerStyle(DefaultPickerStyle()) + Picker("History Return Max", selection: $historyReturnMax ) { + Text("unset").tag(0) + Text("25").tag(25) + Text("50").tag(50) + Text("75").tag(75) + Text("100").tag(100) + } + .pickerStyle(DefaultPickerStyle()) + Picker("History Return Window", selection: $historyReturnWindow ) { + Text("unset").tag(0) + Text("One Hour").tag(60) + Text("Two Hours").tag(120) + Text("Four Hours").tag(240) + Text("Six Hours").tag(360) + } + .pickerStyle(DefaultPickerStyle()) + } + } + .scrollDismissesKeyboard(.interactively) + .disabled(self.bleManager.connectedPeripheral == nil || node?.detectionSensorConfig == nil) + + Button { + isPresentingSaveConfirm = true + } label: { + Label("save", systemImage: "square.and.arrow.down") + } + .disabled(bleManager.connectedPeripheral == nil || !hasChanges) + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .confirmationDialog( + "are.you.sure", + isPresented: $isPresentingSaveConfirm, + titleVisibility: .visible + ) { + let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) + if connectedNode != nil { + let nodeName = node?.user?.longName ?? "unknown".localized + let buttonText = String.localizedStringWithFormat("save.config %@".localized, nodeName) + Button(buttonText) { + var sfc = ModuleConfig.StoreForwardConfig() + sfc.enabled = self.enabled + sfc.heartbeat = self.heartbeat + sfc.records = UInt32(self.records) + sfc.historyReturnMax = UInt32(self.historyReturnMax) + sfc.historyReturnWindow = UInt32(self.historyReturnWindow) + + let adminMessageId = bleManager.saveStoreForwardModuleConfig(config: sfc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + if adminMessageId > 0 { + // Should show a saved successfully alert once I know that to be true + // for now just disable the button after a successful save + hasChanges = false + goBack() + } } + } + } + message: { + Text("config.save.confirm") + } + .navigationTitle("storeforward.config") + .navigationBarItems(trailing: + ZStack { + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") + }) + .onAppear { + self.bleManager.context = context + setDetectionSensorValues() + + // Need to request a Detection Sensor Module Config from the remote node before allowing changes + if bleManager.connectedPeripheral != nil && node?.detectionSensorConfig == nil { + print("empty store and forward module config") + let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) + if node != nil && connectedNode != nil { + _ = bleManager.requestDetectionSensorModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + } + } + } + .onChange(of: enabled) { newEnabled in + if node != nil && node?.detectionSensorConfig != nil { + if newEnabled != node!.detectionSensorConfig!.enabled { hasChanges = true } + } + } + .onChange(of: heartbeat) { newHeartbeat in + if node != nil && node?.storeForwardConfig != nil { + if newHeartbeat != node!.storeForwardConfig!.heartbeat { hasChanges = true } + } + } + .onChange(of: records) { newRecords in + if node != nil && node?.storeForwardConfig != nil { + if newRecords != node!.storeForwardConfig!.records { hasChanges = true } + } + } + .onChange(of: historyReturnMax) { newHistoryReturnMax in + if node != nil && node?.storeForwardConfig != nil { + if newHistoryReturnMax != node!.storeForwardConfig!.historyReturnMax { hasChanges = true } + } + } + .onChange(of: historyReturnWindow) { newHistoryReturnWindow in + if node != nil && node?.storeForwardConfig != nil { + if newHistoryReturnWindow != node!.storeForwardConfig!.historyReturnWindow { hasChanges = true } + } + } + } + func setDetectionSensorValues() { + self.enabled = (node?.storeForwardConfig?.enabled ?? false) + self.heartbeat = (node?.storeForwardConfig?.heartbeat ?? true) + self.records = Int(node?.storeForwardConfig?.records ?? 50) + self.historyReturnMax = Int(node?.storeForwardConfig?.historyReturnMax ?? 100) + self.historyReturnWindow = Int(node?.storeForwardConfig?.historyReturnWindow ?? 60) + self.hasChanges = false + } +} diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 5b36fe6d..52e70d7c 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -258,6 +258,15 @@ struct Settings: View { } .tag(SettingsSidebar.serialConfig) + NavigationLink { + StoreForwardConfig(node: nodes.first(where: { $0.num == selectedNode })) + } label: { + Image(systemName: "envelope.arrow.triangle.branch") + .symbolRenderingMode(.hierarchical) + Text("storeforward") + } + .tag(SettingsSidebar.serialConfig) + NavigationLink { TelemetryConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { diff --git a/Widgets/Assets.xcassets/logo-black.imageset/Contents.json b/Widgets/Assets.xcassets/m-logo-black.imageset/Contents.json similarity index 100% rename from Widgets/Assets.xcassets/logo-black.imageset/Contents.json rename to Widgets/Assets.xcassets/m-logo-black.imageset/Contents.json diff --git a/Widgets/Assets.xcassets/logo-black.imageset/Mesh_Logo_Black.svg b/Widgets/Assets.xcassets/m-logo-black.imageset/Mesh_Logo_Black.svg similarity index 100% rename from Widgets/Assets.xcassets/logo-black.imageset/Mesh_Logo_Black.svg rename to Widgets/Assets.xcassets/m-logo-black.imageset/Mesh_Logo_Black.svg diff --git a/Widgets/Assets.xcassets/logo-black.imageset/Mesh_Logo_Black_Large.svg b/Widgets/Assets.xcassets/m-logo-black.imageset/Mesh_Logo_Black_Large.svg similarity index 100% rename from Widgets/Assets.xcassets/logo-black.imageset/Mesh_Logo_Black_Large.svg rename to Widgets/Assets.xcassets/m-logo-black.imageset/Mesh_Logo_Black_Large.svg diff --git a/Widgets/Assets.xcassets/logo-black.imageset/Mesh_Logo_Black_Small.svg b/Widgets/Assets.xcassets/m-logo-black.imageset/Mesh_Logo_Black_Small.svg similarity index 100% rename from Widgets/Assets.xcassets/logo-black.imageset/Mesh_Logo_Black_Small.svg rename to Widgets/Assets.xcassets/m-logo-black.imageset/Mesh_Logo_Black_Small.svg diff --git a/Widgets/Assets.xcassets/logo-white.imageset/Contents.json b/Widgets/Assets.xcassets/m-logo-white.imageset/Contents.json similarity index 100% rename from Widgets/Assets.xcassets/logo-white.imageset/Contents.json rename to Widgets/Assets.xcassets/m-logo-white.imageset/Contents.json diff --git a/Widgets/Assets.xcassets/logo-white.imageset/Mesh_Logo_White.svg b/Widgets/Assets.xcassets/m-logo-white.imageset/Mesh_Logo_White.svg similarity index 100% rename from Widgets/Assets.xcassets/logo-white.imageset/Mesh_Logo_White.svg rename to Widgets/Assets.xcassets/m-logo-white.imageset/Mesh_Logo_White.svg diff --git a/Widgets/Assets.xcassets/logo-white.imageset/Mesh_Logo_White_Large.svg b/Widgets/Assets.xcassets/m-logo-white.imageset/Mesh_Logo_White_Large.svg similarity index 100% rename from Widgets/Assets.xcassets/logo-white.imageset/Mesh_Logo_White_Large.svg rename to Widgets/Assets.xcassets/m-logo-white.imageset/Mesh_Logo_White_Large.svg diff --git a/Widgets/Assets.xcassets/logo-white.imageset/Mesh_Logo_White_Small.svg b/Widgets/Assets.xcassets/m-logo-white.imageset/Mesh_Logo_White_Small.svg similarity index 100% rename from Widgets/Assets.xcassets/logo-white.imageset/Mesh_Logo_White_Small.svg rename to Widgets/Assets.xcassets/m-logo-white.imageset/Mesh_Logo_White_Small.svg diff --git a/Widgets/WidgetsLiveActivity.swift b/Widgets/WidgetsLiveActivity.swift index a9ffa68e..dd72d684 100644 --- a/Widgets/WidgetsLiveActivity.swift +++ b/Widgets/WidgetsLiveActivity.swift @@ -76,7 +76,7 @@ struct WidgetsLiveActivity: Widget { } } compactLeading: { - Image("logo-black") + Image("m-logo-black") .resizable() .frame(width: 30.0) .padding(4) @@ -87,7 +87,7 @@ struct WidgetsLiveActivity: Widget { .foregroundColor(Color("LightIndigo")) .frame(width: 40) } minimal: { - Image("logo-black") + Image("m-logo-black") .resizable() .frame(width: 24.0) .padding(4) @@ -137,7 +137,7 @@ struct LiveActivityView: View { var body: some View { HStack { - Image(colorScheme == .light ? "logo-black" : "logo-white") + Image(colorScheme == .light ? "m-logo-black" : "m-logo-white") .resizable() .clipShape(ContainerRelativeShape()) .opacity(isLuminanceReduced ? 0.5 : 1.0) diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index 18db64fc..1786ac64 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -256,6 +256,9 @@ "set.region"="Set LoRa Region"; "standard"="Standard"; "standard.muted"="Standard Muted"; +"storeforward"="Store & Forward"; +"storeforward.config"="Store & Forward Config"; +"storeforward.heartbeat"="Send Heartbeat"; "ssid"="SSID"; "tapback"="Tapback Response"; "tapback.heart"="Heart"; diff --git a/swiftlint.geojson b/swiftlint.geojson new file mode 100644 index 00000000..5372466e --- /dev/null +++ b/swiftlint.geojson @@ -0,0 +1,6 @@ +{ + "type": "MultiPolygon", + "coordinates": [ + + ] +} From b4afe788672b068d4ef06d7c9bcf283ba0104bb8 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 26 Aug 2023 20:46:10 -0700 Subject: [PATCH 34/40] Bump version --- Meshtastic.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 772276dd..7866e58c 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1350,7 +1350,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.2.2; + MARKETING_VERSION = 2.2.3; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1384,7 +1384,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.2.2; + MARKETING_VERSION = 2.2.3; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1506,7 +1506,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.2; + MARKETING_VERSION = 2.2.3; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1539,7 +1539,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.2; + MARKETING_VERSION = 2.2.3; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; From b165df94f395a1256184d3fb4b82b2a88d52e54b Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 26 Aug 2023 21:31:50 -0700 Subject: [PATCH 35/40] Update logging emoji for detection sensor, hook up store & forward config. --- Meshtastic/Helpers/MeshPackets.swift | 47 +++++++-------------- Meshtastic/Persistence/UpdateCoreData.swift | 2 +- de.lproj/Localizable.strings | 5 +++ en.lproj/Localizable.strings | 2 + zh-Hans.lproj/Localizable.strings | 5 +++ 5 files changed, 29 insertions(+), 32 deletions(-) diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index a06ee4f4..1e0203a8 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -58,6 +58,8 @@ func moduleConfig (config: ModuleConfig, context: NSManagedObjectContext, nodeNu if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.cannedMessage(config.cannedMessage) { upsertCannedMessagesModuleConfigPacket(config: config.cannedMessage, nodeNum: nodeNum, context: context) + } else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.detectionSensor(config.detectionSensor) { + upsertDetectionSensorModuleConfigPacket(config: config.detectionSensor, nodeNum: nodeNum, context: context) } else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.externalNotification(config.externalNotification) { upsertExternalNotificationModuleConfigPacket(config: config.externalNotification, nodeNum: nodeNum, context: context) } else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.mqtt(config.mqtt) { @@ -68,8 +70,8 @@ func moduleConfig (config: ModuleConfig, context: NSManagedObjectContext, nodeNu upsertSerialModuleConfigPacket(config: config.serial, nodeNum: nodeNum, context: context) } else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.telemetry(config.telemetry) { upsertTelemetryModuleConfigPacket(config: config.telemetry, nodeNum: nodeNum, context: context) - } else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.detectionSensor(config.detectionSensor) { - upsertDetectionSensorModuleConfigPacket(config: config.detectionSensor, nodeNum: nodeNum, context: context) + } else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.storeForward(config.storeForward) { + upsertStoreForwardModuleConfigPacket(config: config.storeForward, nodeNum: nodeNum, context: context) } } @@ -423,65 +425,48 @@ func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { } } else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getChannelResponse(adminMessage.getChannelResponse) { channelPacket(channel: adminMessage.getChannelResponse, fromNum: Int64(packet.from), context: context) - } else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getDeviceMetadataResponse(adminMessage.getDeviceMetadataResponse) { deviceMetadataPacket(metadata: adminMessage.getDeviceMetadataResponse, fromNum: Int64(packet.from), context: context) - } else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getConfigResponse(adminMessage.getConfigResponse) { - let config = adminMessage.getConfigResponse - if config.payloadVariant == Config.OneOf_PayloadVariant.bluetooth(config.bluetooth) { upsertBluetoothConfigPacket(config: config.bluetooth, nodeNum: Int64(packet.from), context: context) - } else if config.payloadVariant == Config.OneOf_PayloadVariant.device(config.device) { upsertDeviceConfigPacket(config: config.device, nodeNum: Int64(packet.from), context: context) - } else if config.payloadVariant == Config.OneOf_PayloadVariant.display(config.display) { upsertDisplayConfigPacket(config: config.display, nodeNum: Int64(packet.from), context: context) - } else if config.payloadVariant == Config.OneOf_PayloadVariant.lora(config.lora) { upsertLoRaConfigPacket(config: config.lora, nodeNum: Int64(packet.from), context: context) - } else if config.payloadVariant == Config.OneOf_PayloadVariant.network(config.network) { upsertNetworkConfigPacket(config: config.network, nodeNum: Int64(packet.from), context: context) - } else if config.payloadVariant == Config.OneOf_PayloadVariant.position(config.position) { upsertPositionConfigPacket(config: config.position, nodeNum: Int64(packet.from), context: context) - } } else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getModuleConfigResponse(adminMessage.getModuleConfigResponse) { - let moduleConfig = adminMessage.getModuleConfigResponse - if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.cannedMessage(moduleConfig.cannedMessage) { upsertCannedMessagesModuleConfigPacket(config: moduleConfig.cannedMessage, nodeNum: Int64(packet.from), context: context) - - } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.externalNotification(moduleConfig.externalNotification) { - upsertExternalNotificationModuleConfigPacket(config: moduleConfig.externalNotification, nodeNum: Int64(packet.from), context: context) - - } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.mqtt(moduleConfig.mqtt) { - upsertMqttModuleConfigPacket(config: moduleConfig.mqtt, nodeNum: Int64(packet.from), context: context) - - } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.rangeTest(moduleConfig.rangeTest) { - upsertRangeTestModuleConfigPacket(config: moduleConfig.rangeTest, nodeNum: Int64(packet.from), context: context) - - } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.serial(moduleConfig.serial) { - upsertSerialModuleConfigPacket(config: moduleConfig.serial, nodeNum: Int64(packet.from), context: context) - - } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.telemetry(moduleConfig.telemetry) { - upsertTelemetryModuleConfigPacket(config: moduleConfig.telemetry, nodeNum: Int64(packet.from), context: context) } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.detectionSensor(moduleConfig.detectionSensor) { upsertDetectionSensorModuleConfigPacket(config: moduleConfig.detectionSensor, nodeNum: Int64(packet.from), context: context) + } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.externalNotification(moduleConfig.externalNotification) { + upsertExternalNotificationModuleConfigPacket(config: moduleConfig.externalNotification, nodeNum: Int64(packet.from), context: context) + } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.mqtt(moduleConfig.mqtt) { + upsertMqttModuleConfigPacket(config: moduleConfig.mqtt, nodeNum: Int64(packet.from), context: context) + } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.rangeTest(moduleConfig.rangeTest) { + upsertRangeTestModuleConfigPacket(config: moduleConfig.rangeTest, nodeNum: Int64(packet.from), context: context) + } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.serial(moduleConfig.serial) { + upsertSerialModuleConfigPacket(config: moduleConfig.serial, nodeNum: Int64(packet.from), context: context) + } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.storeForward(moduleConfig.storeForward) { + upsertStoreForwardModuleConfigPacket(config: moduleConfig.storeForward, nodeNum: Int64(packet.from), context: context) + } else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.telemetry(moduleConfig.telemetry) { + upsertTelemetryModuleConfigPacket(config: moduleConfig.telemetry, nodeNum: Int64(packet.from), context: context) } - } else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getRingtoneResponse(adminMessage.getRingtoneResponse) { let ringtone = adminMessage.getRingtoneResponse upsertRtttlConfigPacket(ringtone: ringtone, nodeNum: Int64(packet.from), context: context) } else { MeshLogger.log("🕸️ MESH PACKET received for Admin App \(try! packet.decoded.jsonString())") } - // Save an ack for the admin message log for each admin message response received as we stopped sending acks if there is also a response to reduce airtime. adminResponseAck(packet: packet, context: context) } diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 169f004d..fa58280b 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -653,7 +653,7 @@ func upsertCannedMessagesModuleConfigPacket(config: Meshtastic.ModuleConfig.Cann func upsertDetectionSensorModuleConfigPacket(config: Meshtastic.ModuleConfig.DetectionSensorConfig, nodeNum: Int64, context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.detectionsensor.config %@".localized, String(nodeNum)) - MeshLogger.log("📈 \(logString)") + MeshLogger.log("🕵️ \(logString)") let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index 16356d1c..90b285a7 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -157,6 +157,7 @@ "mesh.log.display.config %@"="Display Konfiguration empfangen: %@"; "mesh.log.devicemetadata %@"="Anforderung der Geräte Metadaten für %@"; "mesh.log.device.metadata.received %@"="Device Metadata received from: %@"; +"mesh.log.detectionsensor.config %@"="Detection Sensor module config received: %@"; "mesh.log.externalnotification.config %@"="External Notifiation module config received: %@"; "mesh.log.lora.config %@"="LoRa config received: %@"; "mesh.log.lora.config.sent %@"="Sent a LoRa.Config for: %@"; @@ -171,6 +172,7 @@ "mesh.log.routing.message %@ %@"="Routing empfangen für RequestID: %@ Ack Status: %@"; "mesh.log.serial.config %@"="Serial Modul Konfiguration empfangen: %@"; "mesh.log.sharelocation %@"="Sent a Position Packet from the Apple device GPS to node: %@"; +"mesh.log.storeforward.config %@"="Store & Forward module config received: %@"; "mesh.log.telemetry.config %@"="Telemetrie Modul Konfiguration empfangen: %@"; "mesh.log.telemetry.received %@"="Telemetrie empfangen für: %@"; "mesh.log.textmessage.received"="Nachricht von der Textnachricht-App empfangen."; @@ -253,6 +255,9 @@ "set.region"="Setze LoRa Region"; "standard"="Standard"; "standard.muted"="Standard Muted"; +"storeforward"="Store & Forward"; +"storeforward.config"="Store & Forward Config"; +"storeforward.heartbeat"="Send Heartbeat"; "ssid"="SSID"; "tapback"="Tapback Response"; "tapback.heart"="Gehört"; diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index 1786ac64..1f296093 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -160,6 +160,7 @@ "mesh.log.display.config %@"="Display config received: %@"; "mesh.log.devicemetadata %@"="Requesting Device Metadata for %@"; "mesh.log.device.metadata.received %@"="Device Metadata received from: %@"; +"mesh.log.detectionsensor.config %@"="Detection Sensor module config received: %@"; "mesh.log.externalnotification.config %@"="External Notifiation module config received: %@"; "mesh.log.lora.config %@"="LoRa config received: %@"; "mesh.log.lora.config.sent %@"="Sent a LoRa.Config for: %@"; @@ -174,6 +175,7 @@ "mesh.log.routing.message %@ %@"="Routing received for RequestID: %@ Ack Status: %@"; "mesh.log.serial.config %@"="Serial module config received: %@"; "mesh.log.sharelocation %@"="Sent a Position Packet from the Apple device GPS to node: %@"; +"mesh.log.storeforward.config %@"="Store & Forward module config received: %@"; "mesh.log.telemetry.config %@"="Telemetry module config received: %@"; "mesh.log.telemetry.received %@"="Telemetry received for: %@"; "mesh.log.textmessage.received"="Message received from the text message app."; diff --git a/zh-Hans.lproj/Localizable.strings b/zh-Hans.lproj/Localizable.strings index 9ec1cc5d..b57c4fa2 100644 --- a/zh-Hans.lproj/Localizable.strings +++ b/zh-Hans.lproj/Localizable.strings @@ -157,6 +157,7 @@ "mesh.log.display.config %@"="Display config received: %@"; "mesh.log.devicemetadata %@"="Requesting Device Metadata for %@"; "mesh.log.device.metadata.received %@"="Device Metadata admin message received from: %@"; +"mesh.log.detectionsensor.config %@"="Detection Sensor module config received: %@"; "mesh.log.externalnotification.config %@"="External Notifiation module config received: %@"; "mesh.log.lora.config %@"="LoRa config received: %@"; "mesh.log.lora.config.sent %@"="Sent a LoRa.Config for: %@"; @@ -171,6 +172,7 @@ "mesh.log.routing.message %@ %@"="Routing received for RequestID: %@ Ack Status: %@"; "mesh.log.serial.config %@"="Serial module config received: %@"; "mesh.log.sharelocation %@"="Sent a Position Packet from the Apple device GPS to node: %@"; +"mesh.log.storeforward.config %@"="Store & Forward module config received: %@"; "mesh.log.telemetry.config %@"="Telemetry module config received: %@"; "mesh.log.telemetry.received %@"="Telemetry received for: %@"; "mesh.log.textmessage.received"="Message received from the text message app."; @@ -254,6 +256,9 @@ "standard"="标准"; "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"; From a990d7f2bdc9a590c383397fdf666170cc538de0 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 26 Aug 2023 23:17:30 -0700 Subject: [PATCH 36/40] Lint --- Meshtastic.xcodeproj/project.pbxproj | 4 ++ Meshtastic/Enums/AppSettingsEnums.swift | 15 ---- Meshtastic/Enums/HardwareModels.swift | 3 - Meshtastic/Extensions/Color.swift | 5 +- Meshtastic/Extensions/Double.swift | 1 - Meshtastic/Extensions/FileManager.swift | 4 +- Meshtastic/Extensions/String.swift | 4 -- Meshtastic/Extensions/Url.swift | 1 - Meshtastic/Extensions/UserDefaults.swift | 15 ---- Meshtastic/Helpers/LocationHelper.swift | 23 ------ .../Helpers/Map/OfflineTileManager.swift | 40 ++--------- Meshtastic/Helpers/MeshPackets.swift | 8 +-- .../Helpers/Mqtt/MqttClientProxyManager.swift | 29 +------- Meshtastic/Helpers/NetworkManager.swift | 7 +- Meshtastic/MeshtasticApp.swift | 3 +- Meshtastic/MeshtasticAppDelegate.swift | 13 +--- Meshtastic/Model/PeripheralModel.swift | 22 +++--- .../Persistence/MessageEntityExtension.swift | 1 - .../Persistence/PositionEntityExtension.swift | 1 - Meshtastic/Persistence/QueryCoreData.swift | 4 +- Meshtastic/Persistence/UpdateCoreData.swift | 3 - Meshtastic/Views/Bluetooth/Connect.swift | 7 +- Meshtastic/Views/ContentView.swift | 2 - .../Views/Helpers/BatteryLevelCompact.swift | 8 +-- .../Views/Helpers/ConnectedDevice.swift | 1 - .../Views/Helpers/LoRaSignalStrength.swift | 6 -- .../Helpers/LoRaSignalStrengthIndicator.swift | 12 +--- .../Views/Helpers/Node/NodeInfoView.swift | 11 --- .../Views/Map/Custom/LocalMBTileOverlay.swift | 4 +- Meshtastic/Views/Map/Custom/MapButtons.swift | 5 +- .../Views/Map/Custom/MapViewSwiftUI.swift | 72 +++---------------- .../Views/Messages/ChannelMessageList.swift | 2 - .../Views/Nodes/DetectionSensorLog.swift | 15 +--- Meshtastic/Views/Nodes/DeviceMetricsLog.swift | 31 ++++---- .../Views/Nodes/EnvironmentMetricsLog.swift | 9 +-- Meshtastic/Views/Nodes/NodeDetail.swift | 17 ++--- Meshtastic/Views/Nodes/NodeList.swift | 2 - Meshtastic/Views/Nodes/NodeMap.swift | 34 +-------- Meshtastic/Views/Nodes/PositionLog.swift | 22 ------ Meshtastic/Views/Settings/AppSettings.swift | 11 --- .../Views/Settings/Config/DeviceConfig.swift | 36 ++-------- .../Views/Settings/Config/DisplayConfig.swift | 4 -- .../Views/Settings/Config/LoRaConfig.swift | 1 - .../Config/Module/DetectionSensorConfig.swift | 8 +-- .../Settings/Config/Module/MQTTConfig.swift | 4 -- .../Settings/Config/Module/RtttlConfig.swift | 3 - .../Settings/Config/Module/StoreForward.swift | 1 - .../Views/Settings/Config/NetworkConfig.swift | 2 - .../Settings/Config/PositionConfig.swift | 16 +---- Meshtastic/Views/Settings/Firmware.swift | 47 ++---------- Meshtastic/Views/Settings/Settings.swift | 38 +--------- Widgets/BatteryLevel.swift | 3 - Widgets/WidgetsLiveActivity.swift | 2 +- 53 files changed, 92 insertions(+), 550 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 7866e58c..a5e81a19 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -156,6 +156,7 @@ DDDEE5E129DA3E1100A8E078 /* NodeInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDEE5E029DA3E1100A8E078 /* NodeInfoView.swift */; }; DDE0F7C5295F77B700B8AAB3 /* AppSettingsEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */; }; DDF6B2482A9AEBF500BA6931 /* StoreForward.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF6B2472A9AEBF500BA6931 /* StoreForward.swift */; }; + DDF6B24A2A9B09A100BA6931 /* swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = DDF6B2492A9B09A100BA6931 /* swiftlint.yml */; }; DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF924C926FBB953009FE055 /* ConnectedDevice.swift */; }; DDFEB3BB29900C1200EE7472 /* CurrentConditionsCompact.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */; }; /* End PBXBuildFile section */ @@ -369,6 +370,7 @@ DDEE03EC29544A1000FCAD57 /* MeshtasticDataModelV4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV4.xcdatamodel; sourceTree = ""; }; DDF6B2462A9AEB9E00BA6931 /* MeshtasticDataModelV17.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV17.xcdatamodel; sourceTree = ""; }; DDF6B2472A9AEBF500BA6931 /* StoreForward.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreForward.swift; sourceTree = ""; }; + DDF6B2492A9B09A100BA6931 /* swiftlint.yml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.yaml; path = swiftlint.yml; sourceTree = ""; }; DDF924C926FBB953009FE055 /* ConnectedDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedDevice.swift; sourceTree = ""; }; DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentConditionsCompact.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -597,6 +599,7 @@ DDC2E14B26CE248E0042C5E4 = { isa = PBXGroup; children = ( + DDF6B2492A9B09A100BA6931 /* swiftlint.yml */, DDCDC6CD29481FCC004C1DDA /* Localizable.strings */, DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */, DDC2E15626CE248E0042C5E4 /* Meshtastic */, @@ -953,6 +956,7 @@ buildActionMask = 2147483647; files = ( DDC2E15F26CE248F0042C5E4 /* Preview Assets.xcassets in Resources */, + DDF6B24A2A9B09A100BA6931 /* swiftlint.yml in Resources */, DDCDC6CB29481FCC004C1DDA /* Localizable.strings in Resources */, DDDE5A1329AFEAB900490C6C /* Assets.xcassets in Resources */, DDB75A1A2A05EB67006ED576 /* alpha.png in Resources */, diff --git a/Meshtastic/Enums/AppSettingsEnums.swift b/Meshtastic/Enums/AppSettingsEnums.swift index 42f1c445..d2d13979 100644 --- a/Meshtastic/Enums/AppSettingsEnums.swift +++ b/Meshtastic/Enums/AppSettingsEnums.swift @@ -9,16 +9,13 @@ import Foundation import MapKit enum MeshMapTypes: Int, CaseIterable, Identifiable { - case standard = 0 case mutedStandard = 5 case hybrid = 2 case hybridFlyover = 4 case satellite = 1 case satelliteFlyover = 3 - var id: Int { self.rawValue } - var description: String { switch self { case .standard: @@ -36,7 +33,6 @@ enum MeshMapTypes: Int, CaseIterable, Identifiable { } } func MKMapTypeValue() -> MKMapType { - switch self { case .standard: return MKMapType.standard @@ -55,13 +51,10 @@ enum MeshMapTypes: Int, CaseIterable, Identifiable { } enum UserTrackingModes: Int, CaseIterable, Identifiable { - case none = 0 case follow = 1 case followWithHeading = 2 - var id: Int { self.rawValue } - var description: String { switch self { case .none: @@ -80,7 +73,6 @@ enum UserTrackingModes: Int, CaseIterable, Identifiable { } } func MKUserTrackingModeValue() -> MKUserTrackingMode { - switch self { case .none: return MKUserTrackingMode.none @@ -93,7 +85,6 @@ enum UserTrackingModes: Int, CaseIterable, Identifiable { } enum LocationUpdateInterval: Int, CaseIterable, Identifiable { - case fiveSeconds = 5 case tenSeconds = 10 case fifteenSeconds = 15 @@ -103,7 +94,6 @@ enum LocationUpdateInterval: Int, CaseIterable, Identifiable { case fiveMinutes = 300 case tenMinutes = 600 case fifteenMinutes = 900 - var id: Int { self.rawValue } var description: String { switch self { @@ -138,7 +128,6 @@ enum MapLayer: String, CaseIterable, Equatable { } enum MapTileServer: String, CaseIterable, Identifiable { - case openStreetMap case openStreetMapDE case openStreetMapFR @@ -154,8 +143,6 @@ enum MapTileServer: String, CaseIterable, Identifiable { var id: String { self.rawValue } var attribution: String { switch self { - - case .openStreetMap: return "Map and data © [OpenStreetMap](http://www.openstreetmap.org) and contributors, [CC-BY-SA](http://creativecommons.org/licenses/by-sa/2.0/)" case .openStreetMapDE: @@ -276,7 +263,6 @@ enum OverlayType: String, CaseIterable, Equatable { } enum MapOverlayServer: String, CaseIterable, Identifiable { - case baseReReflectivityCurrent case baseReReflectivityOneHourAgo case echoTopsEetCurrent @@ -290,7 +276,6 @@ enum MapOverlayServer: String, CaseIterable, Identifiable { var id: String { self.rawValue } var overlayType: OverlayType { switch self { - case .baseReReflectivityCurrent: return .tileServer case .baseReReflectivityOneHourAgo: diff --git a/Meshtastic/Enums/HardwareModels.swift b/Meshtastic/Enums/HardwareModels.swift index 36575bed..e14cadfd 100644 --- a/Meshtastic/Enums/HardwareModels.swift +++ b/Meshtastic/Enums/HardwareModels.swift @@ -307,9 +307,7 @@ enum HardwareModels: String, CaseIterable, Identifiable { } } - enum HardwarePlatforms: String, CaseIterable, Identifiable { - case none case esp32 case nrf52 @@ -319,7 +317,6 @@ enum HardwarePlatforms: String, CaseIterable, Identifiable { var id: String { self.rawValue } var description: String { switch self { - case .none: return "None" case .esp32: diff --git a/Meshtastic/Extensions/Color.swift b/Meshtastic/Extensions/Color.swift index 1ebd0e44..da47cadc 100644 --- a/Meshtastic/Extensions/Color.swift +++ b/Meshtastic/Extensions/Color.swift @@ -20,7 +20,6 @@ extension Color { } extension UIColor { - /// Returns a boolean indicating if a color is light /// - Returns: true if the color is light func isLight() -> Bool { @@ -28,7 +27,6 @@ extension UIColor { let brightness = ((components[0] * 299) + (components[1] * 587) + (components[2] * 114)) / 1000 return (brightness > 0.5) } - /// Returns a UInt32 from a UIColor /// - Returns: UInt32 var hex: UInt32 { @@ -41,7 +39,6 @@ extension UIColor { value += UInt32(blue * 255) return value } - /// Returns a UIColor from a UInt32 value /// - Parameter hex: UInt32 value to convert to a color /// - Returns: UIColor @@ -49,7 +46,7 @@ extension UIColor { let red = CGFloat((hex & 0xFF0000) >> 16) let green = CGFloat((hex & 0x00FF00) >> 8) let blue = CGFloat((hex & 0x0000FF)) - //print("\(red) - \(green) - \(blue)") + /// print("\(red) - \(green) - \(blue)") self.init(red: red/255.0, green: green/255.0, blue: blue/255.0, alpha: 1.0) } } diff --git a/Meshtastic/Extensions/Double.swift b/Meshtastic/Extensions/Double.swift index c63a9abb..3cb9dd43 100644 --- a/Meshtastic/Extensions/Double.swift +++ b/Meshtastic/Extensions/Double.swift @@ -7,7 +7,6 @@ import Foundation extension Double { - var toBytes: String { let formatter = MeasurementFormatter() let measurement = Measurement(value: self, unit: UnitInformationStorage.bytes) diff --git a/Meshtastic/Extensions/FileManager.swift b/Meshtastic/Extensions/FileManager.swift index 2970bad4..175034ed 100644 --- a/Meshtastic/Extensions/FileManager.swift +++ b/Meshtastic/Extensions/FileManager.swift @@ -9,7 +9,7 @@ import Foundation let allocatedSizeResourceKeys: Set = [ .isRegularFileKey, .fileAllocatedSizeKey, - .totalFileAllocatedSizeKey, + .totalFileAllocatedSizeKey ] public extension FileManager { @@ -26,7 +26,7 @@ public extension FileManager { func allocatedSizeOfDirectory(at directoryURL: URL) -> String { // The error handler simply stores the error and stops traversal - var enumeratorError: Error? = nil + var enumeratorError: Error? func errorHandler(_: URL, error: Error) -> Bool { enumeratorError = error return false diff --git a/Meshtastic/Extensions/String.swift b/Meshtastic/Extensions/String.swift index 30b77102..6ac1672a 100644 --- a/Meshtastic/Extensions/String.swift +++ b/Meshtastic/Extensions/String.swift @@ -29,9 +29,6 @@ extension String { } var localized: String { NSLocalizedString(self, comment: self) } - - - func isEmoji() -> Bool { // Emoji are no more than 4 bytes if self.count > 4 { @@ -45,7 +42,6 @@ extension String { } } } - func onlyEmojis() -> Bool { return count > 0 && !contains { !$0.isEmoji } } diff --git a/Meshtastic/Extensions/Url.swift b/Meshtastic/Extensions/Url.swift index 0cac18a7..a8589e55 100644 --- a/Meshtastic/Extensions/Url.swift +++ b/Meshtastic/Extensions/Url.swift @@ -15,7 +15,6 @@ extension URL { guard resourceValues.isRegularFile ?? false else { return 0 } - return UInt64(resourceValues.totalFileAllocatedSize ?? resourceValues.fileAllocatedSize ?? 0) } } diff --git a/Meshtastic/Extensions/UserDefaults.swift b/Meshtastic/Extensions/UserDefaults.swift index b711f89c..fb73d0cd 100644 --- a/Meshtastic/Extensions/UserDefaults.swift +++ b/Meshtastic/Extensions/UserDefaults.swift @@ -8,7 +8,6 @@ import Foundation extension UserDefaults { - enum Keys: String, CaseIterable { case meshtasticUsername case preferredPeripheralId @@ -26,7 +25,6 @@ extension UserDefaults { func reset() { Keys.allCases.forEach { removeObject(forKey: $0.rawValue) } } - static var meshtasticUsername: String { get { UserDefaults.standard.string(forKey: "meshtasticUsername") ?? "" @@ -44,7 +42,6 @@ extension UserDefaults { UserDefaults.standard.set(newValue, forKey: "preferredPeripheralId") } } - static var provideLocation: Bool { get { UserDefaults.standard.bool(forKey: "provideLocation") @@ -52,7 +49,6 @@ extension UserDefaults { UserDefaults.standard.set(newValue, forKey: "provideLocation") } } - static var provideLocationInterval: Int { get { UserDefaults.standard.integer(forKey: "provideLocationInterval") @@ -61,7 +57,6 @@ extension UserDefaults { UserDefaults.standard.set(newValue, forKey: "provideLocationInterval") } } - static var mapLayer: MapLayer { get { MapLayer(rawValue: UserDefaults.standard.string(forKey: "mapLayer") ?? MapLayer.standard.rawValue) ?? MapLayer.standard @@ -70,7 +65,6 @@ extension UserDefaults { UserDefaults.standard.set(newValue.rawValue, forKey: "mapLayer") } } - static var enableMapRecentering: Bool { get { UserDefaults.standard.bool(forKey: "meshMapRecentering") @@ -79,7 +73,6 @@ extension UserDefaults { UserDefaults.standard.set(newValue, forKey: "meshMapRecentering") } } - static var enableMapNodeHistoryPins: Bool { get { UserDefaults.standard.bool(forKey: "meshMapShowNodeHistory") @@ -88,7 +81,6 @@ extension UserDefaults { UserDefaults.standard.set(newValue, forKey: "meshMapShowNodeHistory") } } - static var enableMapRouteLines: Bool { get { UserDefaults.standard.bool(forKey: "meshMapShowRouteLines") @@ -97,7 +89,6 @@ extension UserDefaults { UserDefaults.standard.set(newValue, forKey: "meshMapShowRouteLines") } } - static var enableOfflineMaps: Bool { get { UserDefaults.standard.bool(forKey: "enableOfflineMaps") @@ -114,17 +105,14 @@ extension UserDefaults { UserDefaults.standard.set(newValue, forKey: "enableOfflineMapsMBTiles") } } - static var mapTileServer: MapTileServer { get { - MapTileServer(rawValue: UserDefaults.standard.string(forKey: "mapTileServer") ?? MapTileServer.openStreetMap.rawValue) ?? MapTileServer.openStreetMap } set { UserDefaults.standard.set(newValue.rawValue, forKey: "mapTileServer") } } - static var enableOverlayServer: Bool { get { UserDefaults.standard.bool(forKey: "enableOverlayServer") @@ -133,17 +121,14 @@ extension UserDefaults { UserDefaults.standard.set(newValue, forKey: "enableOverlayServer") } } - static var mapOverlayServer: MapOverlayServer { get { - MapOverlayServer(rawValue: UserDefaults.standard.string(forKey: "mapOverlayServer") ?? MapOverlayServer.baseReReflectivityCurrent.rawValue) ?? MapOverlayServer.baseReReflectivityCurrent } set { UserDefaults.standard.set(newValue.rawValue, forKey: "mapOverlayServer") } } - static var mapTilesAboveLabels: Bool { get { UserDefaults.standard.bool(forKey: "mapTilesAboveLabels") diff --git a/Meshtastic/Helpers/LocationHelper.swift b/Meshtastic/Helpers/LocationHelper.swift index 1c8c22a1..14e1af70 100644 --- a/Meshtastic/Helpers/LocationHelper.swift +++ b/Meshtastic/Helpers/LocationHelper.swift @@ -2,14 +2,10 @@ import Foundation import CoreLocation class LocationHelper: NSObject, ObservableObject, CLLocationManagerDelegate { - static let shared = LocationHelper() var locationManager = CLLocationManager() - @Published var authorizationStatus: CLAuthorizationStatus? - override init() { - super.init() locationManager.delegate = self locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters @@ -17,53 +13,41 @@ class LocationHelper: NSObject, ObservableObject, CLLocationManagerDelegate { locationManager.allowsBackgroundLocationUpdates = true locationManager.activityType = .otherNavigation } - // Apple Park static let DefaultLocation = CLLocationCoordinate2D(latitude: 37.3346, longitude: -122.0090) static let DefaultAltitude = CLLocationDistance(integerLiteral: 0) static let DefaultSpeed = CLLocationSpeed(integerLiteral: 0) static let DefaultHeading = CLLocationDirection(integerLiteral: 0) - static var currentLocation: CLLocationCoordinate2D { - guard let location = shared.locationManager.location else { return DefaultLocation } return location.coordinate } - static var currentAltitude: CLLocationDistance { - guard let altitude = shared.locationManager.location?.altitude else { return DefaultAltitude } return altitude } - static var currentSpeed: CLLocationSpeed { - guard let speed = shared.locationManager.location?.speed else { return DefaultSpeed } return speed } - static var currentHeading: CLLocationDirection { - guard let heading = shared.locationManager.location?.course else { return DefaultHeading } return heading } - static var currentTimestamp: Date { - guard let timestamp = shared.locationManager.location?.timestamp else { return Date.now } return timestamp } - static var satsInView: Int { // If we have a position we have a sat var sats = 1 @@ -93,26 +77,19 @@ class LocationHelper: NSObject, ObservableObject, CLLocationManagerDelegate { case .authorizedWhenInUse: authorizationStatus = .authorizedWhenInUse locationManager.requestLocation() - break case .restricted: authorizationStatus = .restricted - break case .denied: authorizationStatus = .denied - break case .notDetermined: authorizationStatus = .notDetermined locationManager.requestWhenInUseAuthorization() - break default: break } } - func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { - } - func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { print("Location manager error: \(error.localizedDescription)") } diff --git a/Meshtastic/Helpers/Map/OfflineTileManager.swift b/Meshtastic/Helpers/Map/OfflineTileManager.swift index fe346d18..235ef0b7 100644 --- a/Meshtastic/Helpers/Map/OfflineTileManager.swift +++ b/Meshtastic/Helpers/Map/OfflineTileManager.swift @@ -9,47 +9,35 @@ import Foundation import MapKit class OfflineTileManager: ObservableObject { - enum DownloadStatus { case download, downloading, downloaded } - static let shared = OfflineTileManager() - init() { print("Documents Directory = \(documentsDirectory)") createDirectoriesIfNecessary() } - - // MARK: - Private properties + // MARK: - Private properties private var overlay: MKTileOverlay { MKTileOverlay(urlTemplate: UserDefaults.mapTileServer.tileUrl.count > 1 ? UserDefaults.mapTileServer.tileUrl : MapTileServer.openStreetMap.tileUrl) } - private var documentsDirectory: URL { fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! } - private let fileManager = FileManager.default - - // MARK: - Public property + // MARK: - Public property var progress: Float = 0 var status: DownloadStatus = .download - - // MARK: - Public methods + // MARK: - Public methods func getAllDownloadedSize() -> String { fileManager.allocatedSizeOfDirectory(at: documentsDirectory.appendingPathComponent("tiles")) } - func hasBeenDownloaded(for boundingBox: MKMapRect) -> Bool { getEstimatedDownloadSize(for: boundingBox) == 0 } - func getEstimatedDownloadSize(for boundingBox: MKMapRect) -> Double { let paths = self.computeTileOverlayPaths(boundingBox: boundingBox) let count = self.filterTilesAlreadyExisting(paths: paths).count let size: Double = 30000 // Bytes (average size) return Double(count) * size } - func getDownloadedSize(for mapTileLink: MapTileServer) -> Double { - var accumulatedSize: UInt64 = 0 let mapTiles = try! fileManager.contentsOfDirectory(at: documentsDirectory.appendingPathComponent("tiles"), includingPropertiesForKeys: []) let matchingTiles = mapTiles.filter { fileName in @@ -61,10 +49,8 @@ class OfflineTileManager: ObservableObject { let url = documentsDirectory.appendingPathComponent(tile.absoluteString) accumulatedSize += (try? url.regularFileAllocatedSize()) ?? 0 } - return Double(accumulatedSize) } - func getDownloadedSize(for boundingBox: MKMapRect) -> Double { let paths = self.computeTileOverlayPaths(boundingBox: boundingBox) var accumulatedSize: UInt64 = 0 @@ -75,14 +61,11 @@ class OfflineTileManager: ObservableObject { } return Double(accumulatedSize) } - func removeAll() { try? fileManager.removeItem(at: documentsDirectory.appendingPathComponent("tiles")) createDirectoriesIfNecessary() } - func remove(for mapTileLink: MapTileServer) { - let mapTiles = try! fileManager.contentsOfDirectory(at: documentsDirectory.appendingPathComponent("tiles"), includingPropertiesForKeys: []) let matchingTiles = mapTiles.filter { fileName in let fileNameLower = fileName.absoluteString @@ -93,7 +76,6 @@ class OfflineTileManager: ObservableObject { try? fileManager.removeItem(at: tile.absoluteURL) } } - func remove(for boundingBox: MKMapRect) { let paths = self.computeTileOverlayPaths(boundingBox: boundingBox) for path in paths { @@ -103,7 +85,6 @@ class OfflineTileManager: ObservableObject { } self.status = .download } - /// Download and persist all tiles within the boundingBox func download(boundingBox: MKMapRect, name: String) { NetworkManager.shared.runIfNetwork { @@ -116,18 +97,17 @@ class OfflineTileManager: ObservableObject { self.progress = Float(i) / Float(filteredPaths.count) } DispatchQueue.main.async { - //NotificationManager.shared.sendNotification(title: "\("DownloadedTitle".localized) (\((self.getDownloadedSize(for: boundingBox)).toBytes))", message: "\("Downloaded".localized) (\(name))") + // NotificationManager.shared.sendNotification(title: "\("DownloadedTitle".localized) (\((self.getDownloadedSize(for: boundingBox)).toBytes))", message: "\("Downloaded".localized) (\(name))") self.progress = 0 self.status = .downloaded } } } - func getTileOverlay(for path: MKTileOverlayPath) -> URL { let file = "\(UserDefaults.mapTileServer.id)-z\(path.z)x\(path.x)y\(path.y).png" // Check is tile is already available let tilesUrl = documentsDirectory.appendingPathComponent("tiles").appendingPathComponent(file) - if fileManager.fileExists(atPath: tilesUrl.path){ + if fileManager.fileExists(atPath: tilesUrl.path) { return tilesUrl } else { if UserDefaults.enableOfflineMaps { // Get and persist newTile @@ -137,8 +117,7 @@ class OfflineTileManager: ObservableObject { } } } - - // MARK: - Private methods + // MARK: Private methods private func computeTileOverlayPaths(boundingBox box: MKMapRect, maxZ: Int = 17) -> [MKTileOverlayPath] { var paths = [MKTileOverlayPath]() for z in 1...maxZ { @@ -153,15 +132,13 @@ class OfflineTileManager: ObservableObject { } return paths } - - private func tranformCoordinate(coordinates: CLLocationCoordinate2D , zoom: Int) -> TileCoordinates { + private func tranformCoordinate(coordinates: CLLocationCoordinate2D, zoom: Int) -> TileCoordinates { let lng = coordinates.longitude let lat = coordinates.latitude let tileX = Int(floor((lng + 180) / 360.0 * pow(2.0, Double(zoom)))) let tileY = Int(floor((1 - log( tan( lat * Double.pi / 180.0 ) + 1 / cos( lat * Double.pi / 180.0 )) / Double.pi ) / 2 * pow(2.0, Double(zoom)))) return (tileX, tileY, zoom) } - @discardableResult private func persistLocally(path: MKTileOverlayPath) -> URL { let url = overlay.url(forTilePath: path) let file = "tiles/\(UserDefaults.mapTileServer.id)-z\(path.z)x\(path.x)y\(path.y).png" @@ -174,7 +151,6 @@ class OfflineTileManager: ObservableObject { } return url } - private func filterTilesAlreadyExisting(paths: [MKTileOverlayPath]) -> [MKTileOverlayPath] { paths.filter { let file = "\(UserDefaults.mapTileServer.id)-z\($0.z)x\($0.x)y\($0.y).png" @@ -182,10 +158,8 @@ class OfflineTileManager: ObservableObject { return !fileManager.fileExists(atPath: tilesPath) } } - private func createDirectoriesIfNecessary() { let tiles = documentsDirectory.appendingPathComponent("tiles") try? fileManager.createDirectory(at: tiles, withIntermediateDirectories: true, attributes: [:]) } - } diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 1e0203a8..637b5bd9 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -567,11 +567,9 @@ func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSMana } func storeAndForwardPacket(packet: MeshPacket, connectedNodeNum: Int64, context: NSManagedObjectContext) { - if let storeAndForwardMessage = try? StoreAndForward(serializedData: packet.decoded.payload) { - // RequestResponse + // Request Response switch storeAndForwardMessage.rr { - case .unset: MeshLogger.log("📮 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)") case .routerError: @@ -601,7 +599,7 @@ func storeAndForwardPacket(packet: MeshPacket, connectedNodeNum: Int64, context: MeshLogger.log("🏓 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)") case .clientAbort: MeshLogger.log("🛑 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)") - case .UNRECOGNIZED(_): + case .UNRECOGNIZED: MeshLogger.log("📮 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)") } } @@ -617,7 +615,6 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage MeshLogger.log("📈 \(logString)") } else { // If it is the connected node - } let telemetry = TelemetryEntity(context: context) @@ -790,7 +787,6 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM if channel.index == newMessage.channel { context.refresh(channel, mergeChanges: true) } - if channel.index == newMessage.channel && !channel.mute { // Create an iOS Notification for the received private channel message and schedule it immediately let manager = LocalNotificationManager() diff --git a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift index 0f840c28..4df407b5 100644 --- a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift +++ b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift @@ -16,53 +16,42 @@ protocol MqttClientProxyManagerDelegate: AnyObject { } class MqttClientProxyManager { - // Singleton Instance static let shared = MqttClientProxyManager() private static let defaultKeepAliveInterval: Int32 = 60 weak var delegate: MqttClientProxyManagerDelegate? var mqttClientProxy: CocoaMQTT? var topic = "msh/2/c" - func connectFromConfigSettings(node: NodeInfoEntity) { - let defaultServerAddress = "mqtt.meshtastic.org" let useSsl = node.mqttConfig?.tlsEnabled == true var defaultServerPort = useSsl ? 8883 : 1883 var host = node.mqttConfig?.address if host == nil || host!.isEmpty { host = defaultServerAddress - } - else if host != nil && host!.contains(":") { + } else if host != nil && host!.contains(":") { host = host!.components(separatedBy: ":")[0] defaultServerPort = Int(host!.components(separatedBy: ":")[1])! } - if let host = host { let port = defaultServerPort let username = node.mqttConfig?.username let password = node.mqttConfig?.password - let root = node.mqttConfig?.root?.count ?? 0 > 0 ? node.mqttConfig?.root : "msh" let prefix = root! + "/2/c" topic = prefix + "/#" - let qos = CocoaMQTTQoS(rawValue :UInt8(1))! + let qos = CocoaMQTTQoS(rawValue: UInt8(1))! connect(host: host, port: port, useSsl: useSsl, username: username, password: password, topic: topic, qos: qos, cleanSession: true) } } - func connect(host: String, port: Int, useSsl: Bool, username: String?, password: String?, topic: String?, qos: CocoaMQTTQoS, cleanSession: Bool) { - guard !host.isEmpty else { delegate?.onMqttDisconnected() return } - let clientId = "MeshtasticAppleMqttProxy-" + String(ProcessInfo().processIdentifier) - mqttClientProxy = CocoaMQTT(clientID: clientId, host: host, port: UInt16(port)) if let mqttClient = mqttClientProxy { - mqttClient.enableSSL = useSsl mqttClient.allowUntrustCACertificate = true mqttClient.username = username @@ -83,24 +72,19 @@ class MqttClientProxyManager { delegate?.onMqttError(message: "Mqtt initialization error") } } - func subscribe(topic: String, qos: CocoaMQTTQoS) { print("📲 MQTT Client Proxy subscribed to: " + topic) mqttClientProxy?.subscribe(topic, qos: qos) } - func unsubscribe(topic: String) { mqttClientProxy?.unsubscribe(topic) print("📲 MQTT Client Proxy unsubscribe for: " + topic) } - func publish(message: String, topic: String, qos: CocoaMQTTQoS) { mqttClientProxy?.publish(topic, withString: message, qos: qos) print("📲 MQTT Client Proxy publish for: " + topic) } - func disconnect() { - if let client = mqttClientProxy { client.disconnect() print("📲 MQTT Client Proxy Disconnected") @@ -109,9 +93,7 @@ class MqttClientProxyManager { } extension MqttClientProxyManager: CocoaMQTTDelegate { - func mqtt(_ mqtt: CocoaMQTT, didConnectAck ack: CocoaMQTTConnAck) { - print("📲 MQTT Client Proxy didConnectAck: \(ack)") if ack == .accept { delegate?.onMqttConnected() @@ -139,7 +121,6 @@ extension MqttClientProxyManager: CocoaMQTTDelegate { self.disconnect() } } - func mqttDidDisconnect(_ mqtt: CocoaMQTT, withError err: Error?) { print("mqttDidDisconnect: \(err?.localizedDescription ?? "")") @@ -148,11 +129,9 @@ extension MqttClientProxyManager: CocoaMQTTDelegate { } delegate?.onMqttDisconnected() } - func mqtt(_ mqtt: CocoaMQTT, didPublishMessage message: CocoaMQTTMessage, id: UInt16) { print("📲 MQTT Client Proxy didPublishMessage from MqttClientProxyManager: \(message)") } - func mqtt(_ mqtt: CocoaMQTT, didPublishAck id: UInt16) { print("📲 MQTT Client Proxy didPublishAck from MqttClientProxyManager: \(id)") } @@ -161,19 +140,15 @@ extension MqttClientProxyManager: CocoaMQTTDelegate { delegate?.onMqttMessageReceived(message: message) print("📲 MQTT Client Proxy message received on topic: \(message.topic)") } - func mqtt(_ mqtt: CocoaMQTT, didSubscribeTopics success: NSDictionary, failed: [String]) { print("📲 MQTT Client Proxy didSubscribeTopics: \(success.allKeys.count) topics. failed: \(failed.count) topics") } - func mqtt(_ mqtt: CocoaMQTT, didUnsubscribeTopics topics: [String]) { print("didUnsubscribeTopics: \(topics.joined(separator: ", "))") } - func mqttDidPing(_ mqtt: CocoaMQTT) { print("📲 MQTT Client Proxy mqttDidPing") } - func mqttDidReceivePong(_ mqtt: CocoaMQTT) { print("📲 MQTT Client Proxy mqttDidReceivePong") } diff --git a/Meshtastic/Helpers/NetworkManager.swift b/Meshtastic/Helpers/NetworkManager.swift index 040e06a9..8e24cf49 100644 --- a/Meshtastic/Helpers/NetworkManager.swift +++ b/Meshtastic/Helpers/NetworkManager.swift @@ -9,11 +9,9 @@ import Foundation import Network class NetworkManager { - static let shared = NetworkManager() - - // MARK: - Public methods - func runIfNetwork(completion: @escaping ()->() ) { + // MARK: Public methods + func runIfNetwork(completion: @escaping () -> Void ) { let pathMonitor = NWPathMonitor() pathMonitor.pathUpdateHandler = { guard $0.status == .satisfied else { @@ -26,5 +24,4 @@ class NetworkManager { } pathMonitor.start(queue: DispatchQueue.global(qos: .background)) } - } diff --git a/Meshtastic/MeshtasticApp.swift b/Meshtastic/MeshtasticApp.swift index 3325a45b..b5f24076 100644 --- a/Meshtastic/MeshtasticApp.swift +++ b/Meshtastic/MeshtasticApp.swift @@ -4,7 +4,7 @@ import SwiftUI import CoreData @main -struct MeshtasticAppleApp : App { +struct MeshtasticAppleApp: App { @UIApplicationDelegateAdaptor(MeshtasticAppDelegate.self) var appDelegate let persistenceController = PersistenceController.shared @ObservedObject private var bleManager: BLEManager = BLEManager() @@ -14,7 +14,6 @@ struct MeshtasticAppleApp : App { @State var incomingUrl: URL? @State var channelSettings: String? @StateObject var appState = AppState.shared - var body: some Scene { WindowGroup { ContentView() diff --git a/Meshtastic/MeshtasticAppDelegate.swift b/Meshtastic/MeshtasticAppDelegate.swift index a86e9bf9..0d130ccb 100644 --- a/Meshtastic/MeshtasticAppDelegate.swift +++ b/Meshtastic/MeshtasticAppDelegate.swift @@ -8,31 +8,24 @@ import SwiftUI class MeshtasticAppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate { - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { print("App launched!") UNUserNotificationCenter.current().delegate = self return true } - func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { } - // This method is called when user clicked on the notification - func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) - { + func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { let userInfo = response.notification.request.content.userInfo let targetValue = userInfo["target"] as? String if targetValue == "map" { AppState.shared.tabSelection = Tab.map - } - else if targetValue == "message" { + } else if targetValue == "message" { AppState.shared.tabSelection = Tab.messages - } - else if targetValue == "node" { + } else if targetValue == "node" { AppState.shared.tabSelection = Tab.nodes } - completionHandler() } } diff --git a/Meshtastic/Model/PeripheralModel.swift b/Meshtastic/Model/PeripheralModel.swift index 61cbfcca..ef7230a0 100644 --- a/Meshtastic/Model/PeripheralModel.swift +++ b/Meshtastic/Model/PeripheralModel.swift @@ -12,17 +12,17 @@ struct Peripheral: Identifiable { var lastUpdate: Date var peripheral: CBPeripheral - init(id: String, num: Int64, name: String, shortName: String, longName: String, firmwareVersion: String, rssi: Int, lastUpdate: Date, peripheral: CBPeripheral) { - self.id = id - self.num = num - self.name = name - self.shortName = shortName - self.longName = longName - self.firmwareVersion = firmwareVersion - self.rssi = rssi - self.lastUpdate = lastUpdate - self.peripheral = peripheral - } +// init(id: String, num: Int64, name: String, shortName: String, longName: String, firmwareVersion: String, rssi: Int, lastUpdate: Date, peripheral: CBPeripheral) { +// self.id = id +// self.num = num +// self.name = name +// self.shortName = shortName +// self.longName = longName +// self.firmwareVersion = firmwareVersion +// self.rssi = rssi +// self.lastUpdate = lastUpdate +// self.peripheral = peripheral +// } func getSignalStrength() -> BLESignalStrength { if NSNumber(value: rssi).compare(NSNumber(-65)) == ComparisonResult.orderedDescending { diff --git a/Meshtastic/Persistence/MessageEntityExtension.swift b/Meshtastic/Persistence/MessageEntityExtension.swift index 400b2f97..5f3d76ef 100644 --- a/Meshtastic/Persistence/MessageEntityExtension.swift +++ b/Meshtastic/Persistence/MessageEntityExtension.swift @@ -13,7 +13,6 @@ import MapKit import SwiftUI extension MessageEntity { - var timestamp: Date { let time = messageTimestamp <= 0 ? receivedTimestamp : messageTimestamp return Date(timeIntervalSince1970: TimeInterval(time)) diff --git a/Meshtastic/Persistence/PositionEntityExtension.swift b/Meshtastic/Persistence/PositionEntityExtension.swift index 5f96575c..d9079158 100644 --- a/Meshtastic/Persistence/PositionEntityExtension.swift +++ b/Meshtastic/Persistence/PositionEntityExtension.swift @@ -50,7 +50,6 @@ extension PositionEntity { var annotaton: MKPointAnnotation { let pointAnn = MKPointAnnotation() - if nodeCoordinate != nil { pointAnn.coordinate = nodeCoordinate! } diff --git a/Meshtastic/Persistence/QueryCoreData.swift b/Meshtastic/Persistence/QueryCoreData.swift index d49c8176..c3e718f2 100644 --- a/Meshtastic/Persistence/QueryCoreData.swift +++ b/Meshtastic/Persistence/QueryCoreData.swift @@ -61,7 +61,6 @@ public func getWaypoint(id: Int64, context: NSManagedObjectContext) -> WaypointE return WaypointEntity(context: context) } - public func getDetectionSensorMessages(nodeNum: Int64?, context: NSManagedObjectContext) -> [MessageEntity] { let fetchDetectionMessagesPredicate: NSFetchRequest = NSFetchRequest.init(entityName: "MessageEntity") @@ -75,8 +74,7 @@ public func getDetectionSensorMessages(nodeNum: Int64?, context: NSManagedObject return fetched.filter { message in return message.fromUser?.num == nodeNum! }.reversed() - } - catch { + } catch { return [] } } diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index fa58280b..d1008290 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -118,11 +118,9 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) newNode.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) newNode.snr = packet.rxSnr newNode.rssi = packet.rxRssi - if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) { newNode.channel = Int32(nodeInfoMessage.channel) } - if let newUserMessage = try? User(serializedData: packet.decoded.payload) { let newUser = UserEntity(context: context) newUser.userId = newUserMessage.id @@ -141,7 +139,6 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) fetchedNode[0].rssi = packet.rxRssi if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) { - fetchedNode[0].channel = Int32(nodeInfoMessage.channel) if nodeInfoMessage.hasDeviceMetrics { let telemetry = TelemetryEntity(context: context) diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index c696ee8b..c5633eb7 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -18,7 +18,6 @@ struct Connect: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - //@EnvironmentObject var userSettings: UserSettings @State var node: NodeInfoEntity? @State var isUnsetRegion = false @State var invalidFirmwareVersion = false @@ -180,7 +179,6 @@ struct Connect: View { .imageScale(.large).foregroundColor(.gray) .padding(.trailing) } - Button(action: { if UserDefaults.preferredPeripheralId.count > 0 && peripheral.peripheral.identifier.uuidString != UserDefaults.preferredPeripheralId { presentingSwitchPreferredPeripheral = true @@ -293,7 +291,6 @@ struct Connect: View { func startNodeActivity() { liveActivityStarted = true let timerSeconds = 60 - let deviceMetrics = node?.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")) let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity @@ -319,9 +316,7 @@ struct Connect: View { Task { for activity in Activity.activities { // Check if this is the activity associated with this order. - if activity.attributes.nodeNum == node?.num ?? 0 { - await activity.end(nil, dismissalPolicy: .immediate) - } + if activity.attributes.nodeNum == node?.num ?? 0 { await activity.end(nil, dismissalPolicy: .immediate) } } } } diff --git a/Meshtastic/Views/ContentView.swift b/Meshtastic/Views/ContentView.swift index 32f783b3..146badc1 100644 --- a/Meshtastic/Views/ContentView.swift +++ b/Meshtastic/Views/ContentView.swift @@ -6,9 +6,7 @@ import SwiftUI struct ContentView: View { @StateObject var appState = AppState.shared - var body: some View { - TabView(selection: $appState.tabSelection) { Contacts() .tabItem { diff --git a/Meshtastic/Views/Helpers/BatteryLevelCompact.swift b/Meshtastic/Views/Helpers/BatteryLevelCompact.swift index 8cbd4764..f00dc6e5 100644 --- a/Meshtastic/Views/Helpers/BatteryLevelCompact.swift +++ b/Meshtastic/Views/Helpers/BatteryLevelCompact.swift @@ -13,10 +13,8 @@ struct BatteryLevelCompact: View { var color: Color var body: some View { - - HStack (alignment: .center, spacing: 0) { + HStack(alignment: .center, spacing: 0) { if batteryLevel == 100 { - Image(systemName: "battery.100.bolt") .font(iconFont) .foregroundColor(color) @@ -45,21 +43,17 @@ struct BatteryLevelCompact: View { .font(iconFont) .foregroundColor(color) .symbolRenderingMode(.hierarchical) - } else if batteryLevel! == 0 { - Image(systemName: "battery.0") .font(iconFont) .foregroundColor(.red) .symbolRenderingMode(.hierarchical) } else if batteryLevel! > 100 { - Image(systemName: "powerplug") .font(iconFont) .foregroundColor(color) .symbolRenderingMode(.hierarchical) } - if batteryLevel ?? 0 > 100 { Text("PWD") .font(font) diff --git a/Meshtastic/Views/Helpers/ConnectedDevice.swift b/Meshtastic/Views/Helpers/ConnectedDevice.swift index 8e908cc5..9e4d1085 100644 --- a/Meshtastic/Views/Helpers/ConnectedDevice.swift +++ b/Meshtastic/Views/Helpers/ConnectedDevice.swift @@ -16,7 +16,6 @@ struct ConnectedDevice: View { HStack { if bluetoothOn { if deviceConnected && mqttProxyConnected { - if mqttProxyConnected { Image(systemName: "iphone.gen3.radiowaves.left.and.right.circle.fill") .imageScale(.large) diff --git a/Meshtastic/Views/Helpers/LoRaSignalStrength.swift b/Meshtastic/Views/Helpers/LoRaSignalStrength.swift index 38bf0dab..c415f8fb 100644 --- a/Meshtastic/Views/Helpers/LoRaSignalStrength.swift +++ b/Meshtastic/Views/Helpers/LoRaSignalStrength.swift @@ -8,17 +8,13 @@ import Foundation import SwiftUI struct LoRaSignalStrengthMeter: View { - var snr: Float var rssi: Int32 var preset: ModemPresets var compact: Bool - var body: some View { - let signalStrength = getLoRaSignalStrength(snr: snr, rssi: rssi, preset: preset) let gradient = Gradient(colors: [.red, .orange, .yellow, .green]) - if !compact { VStack { LoRaSignalStrengthIndicator(signalStrength: signalStrength) @@ -92,5 +88,3 @@ struct LoRaSignalStrengthMeter_Previews: PreviewProvider { } } } - - diff --git a/Meshtastic/Views/Helpers/LoRaSignalStrengthIndicator.swift b/Meshtastic/Views/Helpers/LoRaSignalStrengthIndicator.swift index 3df05659..85db8ec1 100644 --- a/Meshtastic/Views/Helpers/LoRaSignalStrengthIndicator.swift +++ b/Meshtastic/Views/Helpers/LoRaSignalStrengthIndicator.swift @@ -72,16 +72,13 @@ private func getColor(signalStrength: LoRaSignalStrength) -> Color { } func getLoRaSignalStrength(snr: Float, rssi: Int32, preset: ModemPresets) -> LoRaSignalStrength { - if rssi > -115 && snr > (preset.snrLimit()) { return .good } else if rssi < -126 && snr < (preset.snrLimit() - 7.5) { return .none } else if rssi <= -120 || snr <= (preset.snrLimit() - 5.5) { return .bad - } else { - return .fair - } + } else { return .fair } } func getRssiColor(rssi: Int32) -> Color { @@ -94,8 +91,7 @@ func getRssiColor(rssi: Int32) -> Color { } else if rssi > -126 { /// Bad return .orange - } else { - // None + } else { // None return .red } } @@ -110,7 +106,5 @@ func getSnrColor(snr: Float, preset: ModemPresets) -> Color { } else if snr >= (preset.snrLimit() - 7.5) { /// Bad return .orange - } else { - return .red - } + } else { return .red } } diff --git a/Meshtastic/Views/Helpers/Node/NodeInfoView.swift b/Meshtastic/Views/Helpers/Node/NodeInfoView.swift index c9570bd4..f6de23a6 100644 --- a/Meshtastic/Views/Helpers/Node/NodeInfoView.swift +++ b/Meshtastic/Views/Helpers/Node/NodeInfoView.swift @@ -21,7 +21,6 @@ struct NodeInfoView: View { var node: NodeInfoEntity var body: some View { - let hwModelString = node.user?.hwModel ?? "UNSET" Divider() @@ -61,7 +60,6 @@ struct NodeInfoView: View { } let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")) if deviceMetrics?.count ?? 0 >= 1 { - let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity VStack(alignment: .center) { BatteryGauge(batteryLevel: Double(mostRecent?.batteryLevel ?? 0)) @@ -127,7 +125,6 @@ struct NodeInfoView: View { VStack(alignment: .center) { CircleText(text: node.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65, fontSize: (node.user?.shortName ?? "???").isEmoji() ? 42 : 20, textColor: UIColor(hex: UInt32(node.num)).isLight() ? .black : .white ) } - if node.user != nil { Divider() VStack { @@ -259,11 +256,3 @@ struct NodeInfoView: View { } } } -struct NodeInfoView_Previews: PreviewProvider { - static var previews: some View { - - VStack { - - } - } -} diff --git a/Meshtastic/Views/Map/Custom/LocalMBTileOverlay.swift b/Meshtastic/Views/Map/Custom/LocalMBTileOverlay.swift index 239a4156..5409dc2b 100644 --- a/Meshtastic/Views/Map/Custom/LocalMBTileOverlay.swift +++ b/Meshtastic/Views/Map/Custom/LocalMBTileOverlay.swift @@ -109,7 +109,7 @@ class LocalMBTileOverlay: MKTileOverlay { } } -//public class CustomMapOverlaySource: MKTileOverlay { +// public class CustomMapOverlaySource: MKTileOverlay { // // // requires folder: tiles/{mapName}/z/y/y,{tileType} // private var parent: MapViewSwiftUI @@ -150,4 +150,4 @@ class LocalMBTileOverlay: MKTileOverlay { // return URL(string: urlstring)! // } // } -//} +// } diff --git a/Meshtastic/Views/Map/Custom/MapButtons.swift b/Meshtastic/Views/Map/Custom/MapButtons.swift index a2dd3e58..d110cb12 100644 --- a/Meshtastic/Views/Map/Custom/MapButtons.swift +++ b/Meshtastic/Views/Map/Custom/MapButtons.swift @@ -12,9 +12,8 @@ struct MapButtons: View { let width: CGFloat = 45 @Binding var tracking: UserTrackingModes @Binding var isPresentingInfoSheet: Bool - var body: some View { - VStack() { + VStack { let impactLight = UIImpactFeedbackGenerator(style: .light) Button(action: { self.isPresentingInfoSheet.toggle() @@ -46,7 +45,6 @@ struct MapButtons: View { .cornerRadius(8) .shadow(radius: 1) .offset(x: 3, y: 25) - } } @@ -61,7 +59,6 @@ struct MapControl_Previews: PreviewProvider { MapButtons(tracking: $tracking, isPresentingInfoSheet: $isPresentingInfoSheet) .environment(\.colorScheme, .dark) } - .previewLayout(.fixed(width: 60, height: 100)) } } diff --git a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift index d8c1d001..1e0a767b 100644 --- a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift @@ -22,31 +22,24 @@ func degreesToRadians(_ number: Double) -> Double { var currentMapLayer: MapLayer? struct MapViewSwiftUI: UIViewRepresentable { - var onLongPress: (_ waypointCoordinate: CLLocationCoordinate2D) -> Void var onWaypointEdit: (_ waypointId: Int ) -> Void - let mapView = MKMapView() // Parameters let selectedMapLayer: MapLayer let selectedWeatherLayer: MapOverlayServer = UserDefaults.mapOverlayServer let positions: [PositionEntity] let waypoints: [WaypointEntity] - let userTrackingMode: MKUserTrackingMode let showNodeHistory: Bool let showRouteLines: Bool - let mapViewType: MKMapType = MKMapType.standard - // Offline Map Tiles @AppStorage("lastUpdatedLocalMapFile") private var lastUpdatedLocalMapFile = 0 @State private var loadedLastUpdatedLocalMapFile = 0 var customMapOverlay: CustomMapOverlay? @State private var presentCustomMapOverlayHash: CustomMapOverlay? - // MARK: Private methods - private func configureMap(mapView: MKMapView) { // Map View Parameters mapView.mapType = mapViewType @@ -64,7 +57,7 @@ struct MapViewSwiftUI: UIViewRepresentable { mapView.setUserTrackingMode(userTrackingMode, animated: true) if userTrackingMode == MKUserTrackingMode.none { if latest.count == 1 { - mapView.fit(annotations:showNodeHistory ? positions : latest, andShow: false) + mapView.fit(annotations: showNodeHistory ? positions: latest, andShow: false) } else { mapView.fitAllAnnotations() } @@ -100,7 +93,6 @@ struct MapViewSwiftUI: UIViewRepresentable { #endif #endif } - private func setMapBaseLayer(mapView: MKMapView) { // Avoid refreshing UI if selectedLayer has not changed guard currentMapLayer != selectedMapLayer else { return } @@ -128,9 +120,7 @@ struct MapViewSwiftUI: UIViewRepresentable { mapView.mapType = .standard } } - private func setMapOverlays(mapView: MKMapView) { - // Weather radar if UserDefaults.enableOverlayServer { let locale = Locale.current @@ -143,16 +133,12 @@ struct MapViewSwiftUI: UIViewRepresentable { } } } - private func setMbtilesOverlay(mapView: MKMapView) { - // MBTiles Offline if UserDefaults.enableOfflineMaps && UserDefaults.enableOfflineMapsMBTiles { - if self.customMapOverlay != self.presentCustomMapOverlayHash || self.loadedLastUpdatedLocalMapFile != self.lastUpdatedLocalMapFile { mapView.removeOverlays(mapView.overlays) if self.customMapOverlay != nil { - let fileManager = FileManager.default let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! let tilePath = documentsDirectory.appendingPathComponent("offline_map.mbtiles", isDirectory: false).path @@ -173,11 +159,10 @@ struct MapViewSwiftUI: UIViewRepresentable { } } } - private func setGeoJsonOverlay(mapView: MKMapView) { - - guard let geoJsonFileUrl = URL(string: "https://raw.githubusercontent.com/johan/world.geo.json/master/countries.geo.json"), // Bundle.main.url(forResource: "location", withExtension: "geojson"), - //guard let geoJsonFileUrl = URL(string: "https://hrbrmstr.github.io/noaa-alerts-sp-to-geojson/current-all.geojson"), + guard let geoJsonFileUrl = URL(string: "https://raw.githubusercontent.com/johan/world.geo.json/master/countries.geo.json"), + // Bundle.main.url(forResource: "location", withExtension: "geojson"), + // guard let geoJsonFileUrl = URL(string: "https://hrbrmstr.github.io/noaa-alerts-sp-to-geojson/current-all.geojson"), let geoJsonData = try? Data.init(contentsOf: geoJsonFileUrl) else { fatalError("Failure to fetch the file.") } @@ -188,21 +173,20 @@ struct MapViewSwiftUI: UIViewRepresentable { objs.forEach { (feature) in guard let geometry = feature.geometry.first, let propData = feature.properties else { - return; + return } // Check if it is MKPolygon if let polygon = geometry as? MKPolygon { let polygonInfo = try? JSONDecoder.init().decode(PolygonInfo.self, from: propData) mapView.addOverlay(polygon) - //self.view?.render(overlay: polygon, info: polygonInfo) + // self.view?.render(overlay: polygon, info: polygonInfo) } // Check if it is MKPolyline if let polyline = geometry as? MKPolyline { mapView.addOverlay(polyline, level: .aboveLabels) - //let polylineInfo = try? JSONDecoder.init().decode(PolylineInfo.self, from: propData) - //self.view?.render(overlay: polyline, info: polylineInfo) + // let polylineInfo = try? JSONDecoder.init().decode(PolylineInfo.self, from: propData) + // self.view?.render(overlay: polyline, info: polylineInfo) } - // Check if it is MKPointAnnotation // if let annotation = geometry as? MKPointAnnotation { // let info = try? JSONDecoder.init().decode(Info.self, from: propData) @@ -214,27 +198,22 @@ struct MapViewSwiftUI: UIViewRepresentable { // } } } - func makeUIView(context: Context) -> MKMapView { currentMapLayer = nil mapView.delegate = context.coordinator self.configureMap(mapView: mapView) return mapView } - func updateUIView(_ mapView: MKMapView, context: Context) { - // Set MBTiles overlay layer setMbtilesOverlay(mapView: mapView) // Set selected map base layer setMapBaseLayer(mapView: mapView) // Set map tile server and weather overlay layers setMapOverlays(mapView: mapView) - let latest = positions .filter { $0.latest == true } .sorted { $0.nodePosition?.num ?? 0 > $1.nodePosition?.num ?? -1 } - // Node Route Lines if showRouteLines { // Remove all existing PolyLine Overlays @@ -245,10 +224,8 @@ struct MapViewSwiftUI: UIViewRepresentable { } var lineIndex = 0 for position in latest { - let nodePositions = positions.filter { $0.nodeCoordinate != nil && $0.nodePosition?.num ?? 0 == position.nodePosition?.num ?? -1 } - let lineCoords = nodePositions.compactMap ({ - (position) -> CLLocationCoordinate2D in + let lineCoords = nodePositions.compactMap({(position) -> CLLocationCoordinate2D in return position.nodeCoordinate ?? LocationHelper.DefaultLocation }) let polyline = MKPolyline(coordinates: lineCoords, count: nodePositions.count) @@ -268,7 +245,6 @@ struct MapViewSwiftUI: UIViewRepresentable { } } } - let annotationCount = waypoints.count + (showNodeHistory ? positions.count : latest.count) if annotationCount != mapView.annotations.count { print("Annotation Count: \(annotationCount) Map Annotations: \(mapView.annotations.count)") @@ -277,9 +253,7 @@ struct MapViewSwiftUI: UIViewRepresentable { } if userTrackingMode == MKUserTrackingMode.none { mapView.showsUserLocation = false - if UserDefaults.enableMapRecentering { - if latest.count == 1 { mapView.fit(annotations: showNodeHistory ? positions : latest, andShow: true) } else { @@ -287,23 +261,18 @@ struct MapViewSwiftUI: UIViewRepresentable { mapView.fitAllAnnotations() } } - } else { mapView.addAnnotations(showNodeHistory ? positions : latest) mapView.showsUserLocation = true } mapView.setUserTrackingMode(userTrackingMode, animated: true) } - func makeCoordinator() -> MapCoordinator { return Coordinator(self) } - final class MapCoordinator: NSObject, MKMapViewDelegate, UIGestureRecognizerDelegate { - var parent: MapViewSwiftUI var longPressRecognizer = UILongPressGestureRecognizer() - init(_ parent: MapViewSwiftUI) { self.parent = parent super.init() @@ -313,16 +282,13 @@ struct MapViewSwiftUI: UIViewRepresentable { self.longPressRecognizer.delegate = self self.parent.mapView.addGestureRecognizer(longPressRecognizer) } - func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { - switch annotation { case let positionAnnotation as PositionEntity: let reuseID = String(positionAnnotation.nodePosition?.num ?? 0) + "-" + String(positionAnnotation.time?.timeIntervalSince1970 ?? 0) let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "node") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: reuseID ) annotationView.tag = -1 annotationView.canShowCallout = true - if positionAnnotation.latest { annotationView.markerTintColor = .systemRed annotationView.displayPriority = .required @@ -345,7 +311,6 @@ struct MapViewSwiftUI: UIViewRepresentable { let distanceFormatter = MKDistanceFormatter() subtitle.text! += "Altitude: \(distanceFormatter.string(fromDistance: Double(positionAnnotation.altitude))) \n" if positionAnnotation.nodePosition?.metadata != nil { - if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.client || DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.clientMute || DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.routerClient { @@ -359,7 +324,6 @@ struct MapViewSwiftUI: UIViewRepresentable { } else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.sensor { annotationView.glyphImage = UIImage(systemName: "sensor") } - let pf = PositionFlags(rawValue: Int(positionAnnotation.nodePosition?.metadata?.positionFlags ?? 3)) if pf.contains(.Satsinview) { subtitle.text! += "Sats in view: \(String(positionAnnotation.satsInView)) \n" @@ -368,7 +332,6 @@ struct MapViewSwiftUI: UIViewRepresentable { subtitle.text! += "Sequence: \(String(positionAnnotation.seqNo)) \n" } if pf.contains(.Heading) { - if parent.userTrackingMode != MKUserTrackingMode.followWithHeading { annotationView.glyphImage = UIImage(systemName: "location.north.fill")?.rotate(radians: Float(degreesToRadians(Double(positionAnnotation.heading)))) subtitle.text! += "Heading: \(String(positionAnnotation.heading)) \n" @@ -384,7 +347,6 @@ struct MapViewSwiftUI: UIViewRepresentable { } subtitle.text! += "Speed: \(formatter.string(from: Measurement(value: Double(positionAnnotation.speed), unit: UnitSpeed.kilometersPerHour))) \n" } - } else { // node metadata is nil annotationView.glyphImage = UIImage(systemName: "flipphone") @@ -445,9 +407,7 @@ struct MapViewSwiftUI: UIViewRepresentable { default: return nil } } - func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) { - switch view.annotation { case let positionAnnotation as PositionEntity: print(positionAnnotation) @@ -456,20 +416,15 @@ struct MapViewSwiftUI: UIViewRepresentable { if view.tag > 0 { parent.onWaypointEdit(view.tag) } - default: break } } - @objc func longPressHandler(_ gesture: UILongPressGestureRecognizer) { - if gesture.state != UIGestureRecognizer.State.ended { return } else if gesture.state != UIGestureRecognizer.State.began { - // Screen Position - CGPoint let location = longPressRecognizer.location(in: self.parent.mapView) - // Map Coordinate - CLLocationCoordinate2D let coordinate = self.parent.mapView.convert(location, toCoordinateFrom: self.parent.mapView) let annotation = MKPointAnnotation() @@ -480,14 +435,11 @@ struct MapViewSwiftUI: UIViewRepresentable { parent.onLongPress(coordinate) } } - public func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { - if let tileOverlay = overlay as? MKTileOverlay { return MKTileOverlayRenderer(tileOverlay: tileOverlay) } else { if let routePolyline = overlay as? MKPolyline { - let titleString = routePolyline.title ?? "0" let renderer = MKPolylineRenderer(polyline: routePolyline) renderer.strokeColor = UIColor(hex: UInt32(titleString) ?? 0) @@ -498,25 +450,21 @@ struct MapViewSwiftUI: UIViewRepresentable { let renderer = MKPolygonRenderer(polygon: polygon) renderer.fillColor = UIColor.purple.withAlphaComponent(0.2) renderer.strokeColor = .purple.withAlphaComponent(0.7) - return renderer } return MKOverlayRenderer(overlay: overlay) } } } - /// is supposed to be located in the folder with the map name public struct DefaultTile: Hashable { let tileName: String let tileType: String - public init(tileName: String, tileType: String) { self.tileName = tileName self.tileType = tileType } } - public struct CustomMapOverlay: Equatable, Hashable { let mapName: String let tileType: String @@ -524,7 +472,6 @@ struct MapViewSwiftUI: UIViewRepresentable { var minimumZoomLevel: Int? var maximumZoomLevel: Int? let defaultTile: DefaultTile? - public init( mapName: String, tileType: String, @@ -540,7 +487,6 @@ struct MapViewSwiftUI: UIViewRepresentable { self.maximumZoomLevel = maximumZoomLevel self.defaultTile = defaultTile } - public init?( mapName: String?, tileType: String, diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index e82b2138..b95d18b9 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -64,7 +64,6 @@ struct ChannelMessageList: View { let markdownText: LocalizedStringKey = LocalizedStringKey.init(message.messagePayloadMarkdown ?? (message.messagePayload ?? "EMPTY MESSAGE")) let linkBlue = Color(red: 0.4627, green: 0.8392, blue: 1) /* #76d6ff */ let isDetectionSensorMessage = message.portNum == Int32(PortNum.detectionSensorApp.rawValue) - Text(markdownText) .tint(linkBlue) .padding(10) @@ -247,7 +246,6 @@ struct ChannelMessageList: View { #if targetEnvironment(macCatalyst) HStack { Spacer() - Button { let bell = "🔔 Alert Bell Character! \u{7}" print(bell) diff --git a/Meshtastic/Views/Nodes/DetectionSensorLog.swift b/Meshtastic/Views/Nodes/DetectionSensorLog.swift index f8dd841d..b4b25b2f 100644 --- a/Meshtastic/Views/Nodes/DetectionSensorLog.swift +++ b/Meshtastic/Views/Nodes/DetectionSensorLog.swift @@ -9,18 +9,14 @@ import SwiftUI import Charts struct DetectionSensorLog: View { - @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - @State private var isPresentingClearLogConfirm: Bool = false @State var isExporting = false @State var exportString = "" - var node: NodeInfoEntity var body: some View { - let oneDayAgo = Calendar.current.date(byAdding: .day, value: -1, to: Date()) let detections = getDetectionSensorMessages(nodeNum: node.num, context: context) let chartData = detections @@ -28,10 +24,8 @@ struct DetectionSensorLog: View { .sorted { $0.timestamp < $1.timestamp } NavigationStack { - if chartData.count > 0 { GroupBox(label: Label("\(detections.count) Total Detection Events", systemImage: "sensor")) { - Chart { ForEach(chartData, id: \.self) { point in Plot { @@ -62,7 +56,7 @@ struct DetectionSensorLog: View { .chartXAxis(.automatic) .chartYScale(domain: 0...20) .chartForegroundStyleScale([ - "Detection events" : .green, + "Detection events": .green ]) .chartLegend(position: .automatic, alignment: .bottom) } @@ -71,7 +65,6 @@ struct DetectionSensorLog: View { let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current) let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma").replacingOccurrences(of: ",", with: "") if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { - // Add a table for mac and ipad Table(detections) { TableColumn("Detection event") { d in @@ -149,9 +142,3 @@ struct DetectionSensorLog: View { ) } } -// -//struct DetectionSensorLog_Previews: PreviewProvider { -// static var previews: some View { -// DetectionSensorLog() -// } -//} diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index 60216d14..844de54b 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -8,21 +8,21 @@ import SwiftUI import Charts struct DeviceMetricsLog: View { - + @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - + @State private var isPresentingClearLogConfirm: Bool = false @State var isExporting = false @State var exportString = "" - + @State private var batteryChartColor: Color = .blue @State private var airtimeChartColor: Color = .orange @State private var channelUtilizationChartColor: Color = .green var node: NodeInfoEntity - + var body: some View { - + let oneWeekAgo = Calendar.current.date(byAdding: .day, value: -7, to: Date()) let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")).reversed() as? [TelemetryEntity] ?? [] let chartData = deviceMetrics @@ -30,14 +30,14 @@ struct DeviceMetricsLog: View { .sorted { $0.time! < $1.time! } NavigationStack { - + if chartData.count > 0 { GroupBox(label: Label("\(deviceMetrics.count) Readings Total", systemImage: "chart.xyaxis.line")) { - + Chart { - + ForEach(chartData, id: \.self) { point in - + Plot { LineMark( x: .value("x", point.time!), @@ -48,7 +48,7 @@ struct DeviceMetricsLog: View { .accessibilityValue("X: \(point.time!), Y: \(point.batteryLevel)") .foregroundStyle(batteryChartColor) .interpolationMethod(.catmullRom(alpha: 1.0)) - + Plot { PointMark( x: .value("x", point.time!), @@ -58,11 +58,11 @@ struct DeviceMetricsLog: View { .accessibilityLabel("Line Series") .accessibilityValue("X: \(point.time!), Y: \(point.channelUtilization)") .foregroundStyle(channelUtilizationChartColor) - + RuleMark(y: .value("Limit", 10)) .lineStyle(StrokeStyle(lineWidth: 1, dash: [5, 10])) .foregroundStyle(airtimeChartColor) - + Plot { PointMark( x: .value("x", point.time!), @@ -80,7 +80,7 @@ struct DeviceMetricsLog: View { .chartXAxis(.automatic) .chartYScale(domain: 0...100) .chartForegroundStyleScale([ - "Battery Level" : .blue, + "Battery Level": .blue, "Channel Utilization": .green, "Airtime": .orange ]) @@ -91,9 +91,8 @@ struct DeviceMetricsLog: View { let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current) let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma").replacingOccurrences(of: ",", with: "") if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { - // Add a table for mac and ipad - //Table(Array(deviceMetrics),id: \.self) { + // Table(Array(deviceMetrics),id: \.self) { Table(deviceMetrics) { TableColumn("battery.level") { dm in if dm.batteryLevel > 100 { @@ -119,13 +118,11 @@ struct DeviceMetricsLog: View { } else { ScrollView { let columns = [ - //GridItem(.flexible(minimum: 30, maximum: 60), spacing: 0.1), GridItem(.flexible(minimum: 30, maximum: 45), spacing: 0.1), GridItem(.flexible(minimum: 30, maximum: 50), spacing: 0.1), GridItem(.flexible(minimum: 30, maximum: 70), spacing: 0.1), GridItem(.flexible(minimum: 30, maximum: 65), spacing: 0.1), GridItem(.flexible(minimum: 130, maximum: 200), spacing: 0.1) - ] LazyVGrid(columns: columns, alignment: .leading, spacing: 1) { GridRow { diff --git a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift index f7930c44..4ba0a84f 100644 --- a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift +++ b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift @@ -20,8 +20,6 @@ struct EnvironmentMetricsLog: View { var node: NodeInfoEntity var body: some View { - - let oneWeekAgo = Calendar.current.date(byAdding: .day, value: -7, to: Date()) let environmentMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 1")).reversed() as? [TelemetryEntity] ?? [] let chartData = environmentMetrics @@ -30,12 +28,9 @@ struct EnvironmentMetricsLog: View { let locale = NSLocale.current as NSLocale let localeUnit = locale.object(forKey: NSLocale.Key(rawValue: "kCFLocaleTemperatureUnitKey")) let format: UnitTemperature = localeUnit as? String ?? "Celsius" == "Fahrenheit" ? .fahrenheit : .celsius - NavigationStack { - if chartData.count > 0 { GroupBox(label: Label("\(environmentMetrics.count) Readings Total", systemImage: "chart.xyaxis.line")) { - Chart { ForEach(chartData, id: \.time) { dataPoint in AreaMark( @@ -53,7 +48,6 @@ struct EnvironmentMetricsLog: View { ) .alignsMarkStylesWithPlotArea() .accessibilityHidden(true) - LineMark( x: .value("Time", dataPoint.time!), y: .value("Temperature", dataPoint.temperature.localeTemperature()) @@ -74,12 +68,11 @@ struct EnvironmentMetricsLog: View { }) .chartYScale(domain: format == .celsius ? -20...55 : 0...125) .chartForegroundStyleScale([ - "Temperature" : .clear + "Temperature": .clear ]) .chartLegend(position: .automatic, alignment: .bottom) } } - let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current) let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma").replacingOccurrences(of: ",", with: "") if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 1ffc9997..2f3d51b0 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -49,8 +49,6 @@ struct NodeDetail: View { @State private var attributionLink: URL? @State private var attributionLogo: URL? - - var body: some View { let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) @@ -70,11 +68,11 @@ struct NodeDetail: View { waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: nil, waypointId: Int64(wpId)) } }, - //visibleMapRect: $mapRect, + // visibleMapRect: $mapRect, selectedMapLayer: selectedMapLayer, positions: lastTenThousand, waypoints: Array(waypoints), - //mapViewType: mapType, + // mapViewType: mapType, userTrackingMode: MKUserTrackingMode.none, showNodeHistory: meshMapShowNodeHistory, showRouteLines: meshMapShowRouteLines, @@ -146,13 +144,10 @@ struct NodeDetail: View { } .padding([.top], 20) } - - ScrollView() { + ScrollView { NodeInfoView(node: node) if self.bleManager.connectedPeripheral != nil && node.metadata != nil { - HStack { - if node.metadata?.canShutdown ?? false { Button(action: { @@ -232,22 +227,18 @@ struct NodeDetail: View { }) .onAppear { self.bleManager.context = context - //mapType = .standard// MeshMapTypes(rawValue: meshMapType)?.MKMapTypeValue() ?? .standard + // mapType = .standard// MeshMapTypes(rawValue: meshMapType)?.MKMapTypeValue() ?? .standard } .task(id: node.num) { if !loadedWeather { do { - if node.positions?.count ?? 0 > 0 { - let mostRecent = node.positions?.lastObject as? PositionEntity - let weather = try await WeatherService.shared.weather(for: mostRecent?.nodeLocation ?? CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude)) condition = weather.currentWeather.condition temperature = weather.currentWeather.temperature humidity = Int(weather.currentWeather.humidity * 100) symbolName = weather.currentWeather.symbolName - let attribution = try await WeatherService.shared.attribution attributionLink = attribution.legalPageURL attributionLogo = colorScheme == .light ? attribution.combinedMarkLightURL : attribution.combinedMarkDarkURL diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 793b69c3..2756d43b 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -12,7 +12,6 @@ import SwiftUI import CoreLocation struct NodeList: View { - @State private var searchText = "" var nodesQuery: Binding { Binding { @@ -129,4 +128,3 @@ struct NodeList: View { .searchable(text: nodesQuery, prompt: "Find a node") } } - diff --git a/Meshtastic/Views/Nodes/NodeMap.swift b/Meshtastic/Views/Nodes/NodeMap.swift index eb830668..7764256f 100644 --- a/Meshtastic/Views/Nodes/NodeMap.swift +++ b/Meshtastic/Views/Nodes/NodeMap.swift @@ -11,13 +11,10 @@ import CoreLocation import CoreData struct NodeMap: View { - @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - @ObservedObject var tileManager = OfflineTileManager.shared @StateObject var appState = AppState.shared - @State var selectedMapLayer: MapLayer = UserDefaults.mapLayer @State var enableMapRecentering: Bool = UserDefaults.enableMapRecentering @State var enableMapRouteLines: Bool = UserDefaults.enableMapRouteLines @@ -28,35 +25,26 @@ struct NodeMap: View { @State var enableOverlayServer: Bool = UserDefaults.enableOverlayServer @State var selectedOverlayServer: MapOverlayServer = UserDefaults.mapOverlayServer @State var mapTilesAboveLabels: Bool = UserDefaults.mapTilesAboveLabels - let fromDate: NSDate = Calendar.current.date(byAdding: .month, value: -1, to: Date())! as NSDate - @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "time", ascending: true)], predicate: NSPredicate(format: "time >= %@ && nodePosition != nil", Calendar.current.date(byAdding: .day, value: -7, to: Date())! as NSDate), animation: .none) private var positions: FetchedResults - @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)], predicate: NSPredicate( format: "expire == nil || expire >= %@", Date() as NSDate ), animation: .none) private var waypoints: FetchedResults @State var waypointCoordinate: WaypointCoordinate? - @State var selectedTracking: UserTrackingModes = .none - @State var isPresentingInfoSheet: Bool = false - @State private var customMapOverlay: MapViewSwiftUI.CustomMapOverlay? = MapViewSwiftUI.CustomMapOverlay( mapName: "offlinemap", tileType: "png", canReplaceMapContent: true ) - var body: some View { - NavigationStack { ZStack { - MapViewSwiftUI( onLongPress: { coord in waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: coord, waypointId: 0) @@ -74,16 +62,13 @@ struct NodeMap: View { customMapOverlay: self.customMapOverlay ) VStack(alignment: .trailing) { - HStack(alignment: .top) { Spacer() MapButtons(tracking: $selectedTracking, isPresentingInfoSheet: $isPresentingInfoSheet) .padding(.trailing, 8) .padding(.top, 16) } - Spacer() - } } .ignoresSafeArea(.all, edges: [.top, .leading, .trailing]) @@ -112,9 +97,7 @@ struct NodeMap: View { } .padding(.top, 5) .padding(.bottom, 5) - Toggle(isOn: $enableMapRecentering) { - Label("map.recentering", systemImage: "camera.metering.center.weighted") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) @@ -122,9 +105,7 @@ struct NodeMap: View { self.enableMapRecentering.toggle() UserDefaults.enableMapRecentering = self.enableMapRecentering } - Toggle(isOn: $enableMapNodeHistoryPins) { - Label("Show Node History", systemImage: "building.columns.fill") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) @@ -132,9 +113,7 @@ struct NodeMap: View { self.enableMapNodeHistoryPins.toggle() UserDefaults.enableMapNodeHistoryPins = self.enableMapNodeHistoryPins } - Toggle(isOn: $enableMapRouteLines) { - Label("Show Route Lines", systemImage: "road.lanes") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) @@ -142,12 +121,9 @@ struct NodeMap: View { self.enableMapRouteLines.toggle() UserDefaults.enableMapRouteLines = self.enableMapRouteLines } - let locale = Locale.current if locale.region?.identifier ?? "no locale" == "US" { - Toggle(isOn: $enableOverlayServer) { - Label("Show Weather", systemImage: "cloud.fill") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) @@ -155,7 +131,6 @@ struct NodeMap: View { self.enableOverlayServer.toggle() UserDefaults.enableOverlayServer = self.enableOverlayServer } - if enableOverlayServer { Picker(selection: $selectedOverlayServer, label: Text("Radar")) { @@ -180,8 +155,8 @@ struct NodeMap: View { Text("Enable Offline Maps") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - .onChange(of: (enableOfflineMaps)) { newEnableOfflineMaps in - UserDefaults.enableOfflineMaps = enableOfflineMaps + .onChange(of: enableOfflineMaps) { newEnableOfflineMaps in + UserDefaults.enableOfflineMaps = newEnableOfflineMaps if !enableOfflineMaps { if self.selectedMapLayer == .offline { self.selectedMapLayer = .standard @@ -189,10 +164,8 @@ struct NodeMap: View { } } if enableOfflineMaps { - VStack (alignment: .leading) { - + VStack(alignment: .leading) { if !enableOfflineMapsMBTiles { - Picker(selection: $selectedTileServer, label: Text("Tile Server")) { ForEach(MapTileServer.allCases, id: \.self) { tsl in @@ -219,7 +192,6 @@ struct NodeMap: View { self.mapTilesAboveLabels.toggle() UserDefaults.mapTilesAboveLabels = self.mapTilesAboveLabels } - } Divider() Toggle(isOn: $enableOfflineMapsMBTiles) { diff --git a/Meshtastic/Views/Nodes/PositionLog.swift b/Meshtastic/Views/Nodes/PositionLog.swift index 011c461d..bc11b5fd 100644 --- a/Meshtastic/Views/Nodes/PositionLog.swift +++ b/Meshtastic/Views/Nodes/PositionLog.swift @@ -7,7 +7,6 @@ import SwiftUI struct PositionLog: View { - @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @Environment(\.verticalSizeClass) var verticalSizeClass: UserInterfaceSizeClass? @@ -16,24 +15,18 @@ struct PositionLog: View { let result = (verticalSizeClass == .regular || verticalSizeClass == .compact) && horizontalSizeClass == .compact return result } - @State var isExporting = false @State var exportString = "" - var node: NodeInfoEntity - @State private var isPresentingClearLogConfirm = false @State private var sortOrder = [KeyPathComparator(\PositionEntity.latitude)] - var body: some View { - NavigationStack { let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current) let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma").replacingOccurrences(of: ",", with: "") if UIDevice.current.userInterfaceIdiom == .pad && !useGrid || UIDevice.current.userInterfaceIdiom == .mac { // Add a table for mac and ipad let positions = node.positions?.reversed() as? [PositionEntity] ?? [] - Table(positions) { TableColumn("Latitude") { position in Text(String(format: "%.5f", position.latitude ?? 0)) @@ -65,9 +58,7 @@ struct PositionLog: View { } .width(min: 180) } - } else { - ScrollView { // Use a grid on iOS as a table only shows a single column let columns = [ @@ -78,7 +69,6 @@ struct PositionLog: View { GridItem(spacing: 0) ] LazyVGrid(columns: columns, alignment: .leading, spacing: 1) { - GridRow { Text("Latitude") .font(.caption2) @@ -115,9 +105,7 @@ struct PositionLog: View { } .padding(.leading) } - HStack { - Button(role: .destructive) { isPresentingClearLogConfirm = true } label: { @@ -136,18 +124,15 @@ struct PositionLog: View { Button("Delete all positions?", role: .destructive) { if clearPositions(destNum: node.num, context: context) { print("Successfully Cleared Position Log") - } else { print("Clear Position Log Failed") } } } - Button { exportString = positionToCsvFile(positions: node.positions!.array as? [PositionEntity] ?? []) isExporting = true } label: { - Label("save", systemImage: "square.and.arrow.down") } .buttonStyle(.bordered) @@ -162,14 +147,10 @@ struct PositionLog: View { contentType: .commaSeparatedText, defaultFilename: String("\(node.user?.longName ?? "Node") Position Log"), onCompletion: { result in - if case .success = result { - print("Position log download succeeded.") self.isExporting = false - } else { - print("Position log download failed: \(result).") } } @@ -177,13 +158,10 @@ struct PositionLog: View { } .navigationTitle("Position Log \(node.positions?.count ?? 0) Points") .navigationBarItems(trailing: - ZStack { - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") }) .onAppear { - self.bleManager.context = context } } diff --git a/Meshtastic/Views/Settings/AppSettings.swift b/Meshtastic/Views/Settings/AppSettings.swift index 4ad86d70..ad094258 100644 --- a/Meshtastic/Views/Settings/AppSettings.swift +++ b/Meshtastic/Views/Settings/AppSettings.swift @@ -5,7 +5,6 @@ import SwiftProtobuf import MapKit struct AppSettings: View { - @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @ObservedObject var tileManager = OfflineTileManager.shared @@ -16,12 +15,10 @@ struct AppSettings: View { @State var provideLocationInterval: Int = UserDefaults.provideLocationInterval @State private var isPresentingCoreDataResetConfirm = false @State private var isPresentingDeleteMapTilesConfirm = false - var body: some View { VStack { Form { Section(header: Text("user.details")) { - HStack { Label("Name", systemImage: "person.crop.rectangle.fill") TextField("Username", text: $meshtasticUsername) @@ -31,7 +28,6 @@ struct AppSettings: View { .disableAutocorrection(true) .listRowSeparator(.visible) } - Section(header: Text("phone.gps")) { let accuracy = Measurement(value: locationHelper.locationManager.location?.horizontalAccuracy ?? 300, unit: UnitLength.meters) let altitiude = Measurement(value: locationHelper.locationManager.location?.altitude ?? 0, unit: UnitLength.meters) @@ -57,15 +53,12 @@ struct AppSettings: View { Label("Speed \(speed.formatted())", systemImage: "speedometer") .font(.footnote) } - } Section(header: Text("Location Settings")) { - Toggle(isOn: $provideLocation) { Label("provide.location", systemImage: "location.circle.fill") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - if UserDefaults.provideLocation { VStack { Picker("update.interval", selection: $provideLocationInterval) { @@ -82,10 +75,8 @@ struct AppSettings: View { .foregroundColor(.gray) } } - } Section(header: Text("App Data")) { - Button { isPresentingCoreDataResetConfirm = true } label: { @@ -124,9 +115,7 @@ struct AppSettings: View { print("delete all tiles") } } - ForEach(MapTileServer.allCases, id: \.self) { tsl in - Button { tileManager.remove(for: tsl) totalDownloadedTileSize = tileManager.getAllDownloadedSize() diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index 6582a1a2..d0a3c609 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -7,18 +7,18 @@ import SwiftUI struct DeviceConfig: View { - + @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @Environment(\.dismiss) private var goBack - + var node: NodeInfoEntity? - + @State private var isPresentingNodeDBResetConfirm = false @State private var isPresentingFactoryResetConfirm = false @State private var isPresentingSaveConfirm = false @State var hasChanges = false - + @State var deviceRole = 0 @State var buzzerGPIO = 0 @State var buttonGPIO = 0 @@ -27,17 +27,14 @@ struct DeviceConfig: View { @State var rebroadcastMode = 0 @State var doubleTapAsButtonPress = false @State var isManaged = false - + var body: some View { - VStack { - Form { if node != nil && node?.metadata == nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 { Text("There has been no response to a request for device metadata over the admin channel for this node.") .font(.callout) .foregroundColor(.orange) - } else if node != nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 { // Let users know what is going on if they are using remote admin and don't have the config yet if node?.deviceConfig == nil { @@ -60,7 +57,6 @@ struct DeviceConfig: View { .foregroundColor(.orange) } Section(header: Text("options")) { - Picker("Device Role", selection: $deviceRole ) { ForEach(DeviceRoles.allCases) { dr in Text(dr.name) @@ -71,7 +67,6 @@ struct DeviceConfig: View { Text(DeviceRoles(rawValue: deviceRole)?.description ?? "") .foregroundColor(.gray) .font(.caption) - Picker("Rebroadcast Mode", selection: $rebroadcastMode ) { ForEach(RebroadcastModes.allCases) { rm in Text(rm.name) @@ -82,14 +77,13 @@ struct DeviceConfig: View { Text(RebroadcastModes(rawValue: rebroadcastMode)?.description ?? "") .foregroundColor(.gray) .font(.caption) - Toggle(isOn: $doubleTapAsButtonPress) { Label("Double Tap as Button", systemImage: "hand.tap") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) Text("Treat double tap on supported accelerometers as a user button press.") .font(.caption) - + Toggle(isOn: $isManaged) { Label("Managed Device", systemImage: "gearshape.arrow.triangle.2.circlepath") } @@ -97,24 +91,17 @@ struct DeviceConfig: View { Text("Enabling Managed mode will restrict access to all radio configurations, such as short/long names, regions, channels, modules, etc. and will only be accessible through the Admin channel. To avoid being locked out, make sure the Admin channel is working properly before enabling it.") .font(.caption) } - Section(header: Text("Debug")) { - Toggle(isOn: $serialEnabled) { - Label("Serial Console", systemImage: "terminal") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - Toggle(isOn: $debugLogEnabled) { - Label("Debug Log", systemImage: "ant.fill") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } - Section(header: Text("GPIO")) { - Picker("Button GPIO", selection: $buttonGPIO) { ForEach(0..<46) { if $0 == 0 { @@ -136,14 +123,11 @@ struct DeviceConfig: View { } .pickerStyle(DefaultPickerStyle()) } - } .disabled(self.bleManager.connectedPeripheral == nil || node?.deviceConfig == nil) - // Only show these buttons for the BLE connected node if bleManager.connectedPeripheral != nil && node?.num ?? -1 == bleManager.connectedPeripheral.num { HStack { - Button("Reset NodeDB", role: .destructive) { isPresentingNodeDBResetConfirm = true } @@ -158,7 +142,6 @@ struct DeviceConfig: View { titleVisibility: .visible ) { Button("Erase all device and app data?", role: .destructive) { - if bleManager.sendNodeDBReset(fromUser: node!.user!, toUser: node!.user!) { bleManager.disconnectPeripheral() clearCoreDataDatabase(context: context) @@ -181,23 +164,19 @@ struct DeviceConfig: View { titleVisibility: .visible ) { Button("Factory reset your device and app? ", role: .destructive) { - if bleManager.sendFactoryReset(fromUser: node!.user!, toUser: node!.user!) { bleManager.disconnectPeripheral() clearCoreDataDatabase(context: context) } else { print("Factory Reset Failed") - } } } } } HStack { - Button { isPresentingSaveConfirm = true - } label: { Label("save", systemImage: "square.and.arrow.down") } @@ -207,7 +186,6 @@ struct DeviceConfig: View { .controlSize(.large) .padding() .confirmationDialog( - "are.you.sure", isPresented: $isPresentingSaveConfirm, titleVisibility: .visible @@ -226,7 +204,6 @@ struct DeviceConfig: View { dc.rebroadcastMode = RebroadcastModes(rawValue: rebroadcastMode)?.protoEnumValue() ?? RebroadcastModes.all.protoEnumValue() dc.doubleTapAsButtonPress = doubleTapAsButtonPress dc.isManaged = isManaged - let adminMessageId = bleManager.saveDeviceConfig(config: dc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) if adminMessageId > 0 { // Should show a saved successfully alert once I know that to be true @@ -251,7 +228,6 @@ struct DeviceConfig: View { .onAppear { self.bleManager.context = context setDeviceValues() - // Need to request a LoRaConfig from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.deviceConfig == nil { print("empty device config") diff --git a/Meshtastic/Views/Settings/Config/DisplayConfig.swift b/Meshtastic/Views/Settings/Config/DisplayConfig.swift index cbcc6e68..98df1d7a 100644 --- a/Meshtastic/Views/Settings/Config/DisplayConfig.swift +++ b/Meshtastic/Views/Settings/Config/DisplayConfig.swift @@ -73,14 +73,12 @@ struct DisplayConfig: View { .toggleStyle(SwitchToggleStyle(tint: .accentColor)) Text("The compass heading on the screen outside of the circle will always point north.") .font(.caption) - Toggle(isOn: $wakeOnTapOrMotion) { Label("Wake Screen on tap or motion", systemImage: "gyroscope") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) Text("Requires that there be an accelerometer on your device.") .font(.caption) - Toggle(isOn: $flipScreen) { Label("Flip Screen", systemImage: "pip.swap") @@ -97,7 +95,6 @@ struct DisplayConfig: View { .pickerStyle(DefaultPickerStyle()) Text("Override automatic OLED screen detection.") .font(.caption) - } Section(header: Text("Timing & Format")) { Picker("Screen on for", selection: $screenOnSeconds ) { @@ -238,7 +235,6 @@ struct DisplayConfig: View { } } func setDisplayValues() { - self.gpsFormat = Int(node?.displayConfig?.gpsFormat ?? 0) self.screenOnSeconds = Int(node?.displayConfig?.screenOnSeconds ?? 0) self.screenCarouselInterval = Int(node?.displayConfig?.screenCarouselInterval ?? 0) diff --git a/Meshtastic/Views/Settings/Config/LoRaConfig.swift b/Meshtastic/Views/Settings/Config/LoRaConfig.swift index 67c7ef57..735cb11f 100644 --- a/Meshtastic/Views/Settings/Config/LoRaConfig.swift +++ b/Meshtastic/Views/Settings/Config/LoRaConfig.swift @@ -57,7 +57,6 @@ struct LoRaConfig: View { Text("LoRa config data was requested over the admin channel but no response has been returned from the remote node. You can check the status of admin message requests in the admin message log.") .font(.callout) .foregroundColor(.orange) - } else { Text("Remote administration for: \(node?.user?.longName ?? "Unknown")") .font(.title3) diff --git a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift index 1f7b1b41..3f77c554 100644 --- a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift @@ -100,7 +100,6 @@ struct DetectionSensorConfig: View { } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } - Section(header: Text("update.interval")) { Picker("Minimum time between detection broadcasts", selection: $minimumBroadcastSecs) { ForEach(UpdateIntervals.allCases) { ui in @@ -110,7 +109,6 @@ struct DetectionSensorConfig: View { .pickerStyle(DefaultPickerStyle()) Text("Mininum time between detection broadcasts. Default is 45 seconds.") .font(.caption) - Picker("State Broadcast Interval", selection: $stateBroadcastSecs) { Text("Never").tag(0) ForEach(UpdateIntervals.allCases) { ui in @@ -120,7 +118,6 @@ struct DetectionSensorConfig: View { .pickerStyle(DefaultPickerStyle()) Text("How often to send detection sensor state to mesh regardless of detection. Default is Never.") .font(.caption) - } } .scrollDismissesKeyboard(.interactively) @@ -155,14 +152,14 @@ struct DetectionSensorConfig: View { dsc.usePullup = self.usePullup dsc.minimumBroadcastSecs = UInt32(self.minimumBroadcastSecs) dsc.stateBroadcastSecs = UInt32(self.stateBroadcastSecs) - let adminMessageId = bleManager.saveDetectionSensorModuleConfig(config: dsc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) if adminMessageId > 0 { // Should show a saved successfully alert once I know that to be true // for now just disable the button after a successful save hasChanges = false goBack() - } } + } + } } } message: { @@ -227,7 +224,6 @@ struct DetectionSensorConfig: View { } } } - func setDetectionSensorValues() { self.enabled = (node?.detectionSensorConfig?.enabled ?? false) self.sendBell = (node?.detectionSensorConfig?.sendBell ?? false) diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index 11dd82ab..a07aa840 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -23,10 +23,8 @@ struct MQTTConfig: View { @State var jsonEnabled = false @State var tlsEnabled = true @State var root = "msh" - var body: some View { - Form { if node != nil && node?.metadata == nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 { Text("There has been no response to a request for device metadata over the admin channel for this node.") @@ -163,7 +161,6 @@ struct MQTTConfig: View { } .keyboardType(.default) .scrollDismissesKeyboard(.interactively) - HStack { Label("Root Topic", systemImage: "tree") TextField("Root Topic", text: $root) @@ -300,7 +297,6 @@ struct MQTTConfig: View { } } } - func setMqttValues() { self.enabled = (node?.mqttConfig?.enabled ?? false) self.proxyToClientEnabled = (node?.mqttConfig?.proxyToClientEnabled ?? false) diff --git a/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift b/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift index b8f91c13..4f96b909 100644 --- a/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift @@ -8,7 +8,6 @@ import SwiftUI struct RtttlConfig: View { - @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @Environment(\.dismiss) private var goBack @@ -49,7 +48,6 @@ struct RtttlConfig: View { .foregroundColor(.orange) } Section(header: Text("options")) { - HStack { Label("ringtone", systemImage: "music.quarternote.3") TextField("Ringtone Transfer Language", text: $ringtone, axis: .vertical) @@ -134,7 +132,6 @@ struct RtttlConfig: View { } } } - func setRtttLConfigValue() { self.ringtone = node?.rtttlConfig?.ringtone ?? "" self.hasChanges = false diff --git a/Meshtastic/Views/Settings/Config/Module/StoreForward.swift b/Meshtastic/Views/Settings/Config/Module/StoreForward.swift index e2572a14..d1e09b84 100644 --- a/Meshtastic/Views/Settings/Config/Module/StoreForward.swift +++ b/Meshtastic/Views/Settings/Config/Module/StoreForward.swift @@ -117,7 +117,6 @@ struct StoreForwardConfig: View { sfc.records = UInt32(self.records) sfc.historyReturnMax = UInt32(self.historyReturnMax) sfc.historyReturnWindow = UInt32(self.historyReturnWindow) - let adminMessageId = bleManager.saveStoreForwardModuleConfig(config: sfc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) if adminMessageId > 0 { // Should show a saved successfully alert once I know that to be true diff --git a/Meshtastic/Views/Settings/Config/NetworkConfig.swift b/Meshtastic/Views/Settings/Config/NetworkConfig.swift index fd6487ed..ab74b1ec 100644 --- a/Meshtastic/Views/Settings/Config/NetworkConfig.swift +++ b/Meshtastic/Views/Settings/Config/NetworkConfig.swift @@ -106,7 +106,6 @@ struct NetworkConfig: View { Text("Enabling WiFi will disable the bluetooth connection to the app.") .font(.callout) } - } if (node != nil && node?.metadata?.hasEthernet ?? false) { Section(header: Text("Ethernet Options")) { @@ -205,7 +204,6 @@ struct NetworkConfig: View { } } } - func setNetworkValues() { self.wifiEnabled = node?.networkConfig?.wifiEnabled ?? false self.wifiSsid = node?.networkConfig?.wifiSsid ?? "" diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index d99043c4..dea77cca 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -113,13 +113,11 @@ struct PositionConfig: View { .pickerStyle(DefaultPickerStyle()) Text("The maximum interval that can elapse without a node sending a position") .font(.caption) - Toggle(isOn: $smartPositionEnabled) { Label("Smart Position Broadcast", systemImage: "location.fill.viewfinder") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - if smartPositionEnabled { Picker("Minimum Broadcast Interval", selection: $broadcastSmartMinimumIntervalSecs) { ForEach(UpdateIntervals.allCases) { at in @@ -129,19 +127,15 @@ struct PositionConfig: View { .pickerStyle(DefaultPickerStyle()) Text("The fastest that position updates will be sent if the minimum distance has been satisfied") .font(.caption) - Picker("Minimum Distance", selection: $broadcastSmartMinimumDistance) { ForEach(10..<151) { - if $0 == 0 { Text("unset") } else { - if $0.isMultiple(of: 5) { Text("\($0)") .tag($0) } - } } } @@ -217,7 +211,6 @@ struct PositionConfig: View { Label("Device GPS Enabled", systemImage: "location") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - if deviceGpsEnabled { Picker("Update Interval", selection: $gpsUpdateInterval) { ForEach(GpsUpdateIntervals.allCases) { ui in @@ -232,9 +225,6 @@ struct PositionConfig: View { } } .pickerStyle(DefaultPickerStyle()) - Text("How long should we try to get our position during each GPS Update Interval attempt?") - .font(.caption) - Picker("GPS Receive GPIO", selection: $rxGpio) { ForEach(0..<46) { if $0 == 0 { @@ -245,7 +235,6 @@ struct PositionConfig: View { } } .pickerStyle(DefaultPickerStyle()) - Picker("GPS Transmit GPIO", selection: $txGpio) { ForEach(0..<46) { if $0 == 0 { @@ -318,8 +307,7 @@ struct PositionConfig: View { pc.positionFlags = UInt32(pf.rawValue) let adminMessageId = bleManager.savePositionConfig(config: pc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) if adminMessageId > 0 { - // Should show a saved successfully alert once I know that to be true - // for now just disable the button after a successful save + // Disable the button after a successful save hasChanges = false goBack() } @@ -457,9 +445,7 @@ struct PositionConfig: View { if existingValue != hvdopFlag { hasChanges = true } } } - func setPositionValues() { - self.smartPositionEnabled = node?.positionConfig?.smartPositionEnabled ?? true self.deviceGpsEnabled = node?.positionConfig?.deviceGpsEnabled ?? true self.rxGpio = Int(node?.positionConfig?.rxGpio ?? 0) diff --git a/Meshtastic/Views/Settings/Firmware.swift b/Meshtastic/Views/Settings/Firmware.swift index e2876280..20985c3f 100644 --- a/Meshtastic/Views/Settings/Firmware.swift +++ b/Meshtastic/Views/Settings/Firmware.swift @@ -15,23 +15,16 @@ import SwiftUI import StoreKit struct Firmware: View { - @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - var node: NodeInfoEntity? - @State var minimumVersion = "2.1.0" @State var version = "" - @State private var firmwareReleaseData: FirmwareRelease = FirmwareRelease() - var body: some View { // NavigationSplitView { NavigationStack { - let hwModel: HardwareModels = HardwareModels.allCases.first(where: { $0.rawValue == node?.user?.hwModel ?? "UNSET" }) ?? HardwareModels.UNSET - VStack(alignment: .leading) { Text("Current Version: \(bleManager.connectedVersion)") .font(.largeTitle) @@ -78,7 +71,7 @@ struct Firmware: View { if connectedNode != nil { if !bleManager.sendRebootOta(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode!.myInfo!.adminIndex) { print("Reboot Failed") - } + } } } label: { Label("Send Reboot OTA", systemImage: "square.and.arrow.down") @@ -98,7 +91,6 @@ struct Firmware: View { Text(hwModel.platform().description) .font(.title3) } - }.padding() VStack(alignment: .leading) { Text("Firmware Releases") @@ -152,85 +144,65 @@ struct Firmware: View { .navigationBarTitleDisplayMode(.inline) } } - func loadData() { - guard let url = URL(string: "https://api.meshtastic.org/github/firmware/list") else { return } - let request = URLRequest(url: url) URLSession.shared.dataTask(with: request) { data, _, _ in - if let data = data { if let response_obj = try? JSONDecoder().decode(FirmwareRelease.self, from: data) { - DispatchQueue.main.async { self.firmwareReleaseData = response_obj } } } - }.resume() } } struct FirmwareRelease: Codable { - var releases: Releases? = Releases() var pullRequests: [PullRequests]? = [] - enum CodingKeys: String, CodingKey { - - case releases = "releases" - case pullRequests = "pullRequests" + case releases = "Releases" + case pullRequests = "Pull Requests" } - init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) - releases = try values.decodeIfPresent(Releases.self, forKey: .releases ) pullRequests = try values.decodeIfPresent([PullRequests].self, forKey: .pullRequests ) } - init() { - } } struct Releases: Codable { - var stable: [Stable]? = [] var alpha: [Alpha]? = [] - enum CodingKeys: String, CodingKey { - case stable = "stable" - case alpha = "alpha" + case stable = "Stable" + case alpha = "Alpha" } - init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) stable = try values.decodeIfPresent([Stable].self, forKey: .stable ) alpha = try values.decodeIfPresent([Alpha].self, forKey: .alpha ) } - init() {} } struct Alpha: Codable { - var id: String? var title: String? var pageUrl: String? var zipUrl: String? - enum CodingKeys: String, CodingKey { case id = "id" case title = "title" case pageUrl = "page_url" case zipUrl = "zip_url" } - init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) id = try values.decodeIfPresent(String.self, forKey: .id ) @@ -238,24 +210,20 @@ struct Alpha: Codable { pageUrl = try values.decodeIfPresent(String.self, forKey: .pageUrl ) zipUrl = try values.decodeIfPresent(String.self, forKey: .zipUrl ) } - init() {} } struct Stable: Codable { - var id: String? var title: String? var pageUrl: String? var zipUrl: String? - enum CodingKeys: String, CodingKey { case id = "id" case title = "title" case pageUrl = "page_url" case zipUrl = "zip_url" } - init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) id = try values.decodeIfPresent(String.self, forKey: .id ) @@ -263,24 +231,20 @@ struct Stable: Codable { pageUrl = try values.decodeIfPresent(String.self, forKey: .pageUrl ) zipUrl = try values.decodeIfPresent(String.self, forKey: .zipUrl ) } - init() {} } struct PullRequests: Codable { - var id: String? var title: String? var pageUrl: String? var zipUrl: String? - enum CodingKeys: String, CodingKey { case id = "id" case title = "title" case pageUrl = "page_url" case zipUrl = "zip_url" } - init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) id = try values.decodeIfPresent(String.self, forKey: .id ) @@ -288,6 +252,5 @@ struct PullRequests: Codable { pageUrl = try values.decodeIfPresent(String.self, forKey: .pageUrl ) zipUrl = try values.decodeIfPresent(String.self, forKey: .zipUrl ) } - init() {} } diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 52e70d7c..5f5d0e2b 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -8,7 +8,6 @@ import SwiftUI struct Settings: View { - @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "user.longName", ascending: true)], animation: .default) @@ -16,9 +15,7 @@ struct Settings: View { @State private var selectedNode: Int = 0 @State private var connectedNodeNum: Int = 0 @State private var initialLoad: Bool = true - @State private var selection: SettingsSidebar = .about - enum SettingsSidebar { case appSettings case shareChannels @@ -42,7 +39,6 @@ struct Settings: View { case adminMessageLog case about } - var body: some View { NavigationSplitView { List { @@ -51,7 +47,6 @@ struct Settings: View { } label: { Image(systemName: "questionmark.app") .symbolRenderingMode(.hierarchical) - Text("about.meshtastic") } .tag(SettingsSidebar.about) @@ -65,7 +60,6 @@ struct Settings: View { .tag(SettingsSidebar.appSettings) let node = nodes.first(where: { $0.num == connectedNodeNum }) if !(node?.deviceConfig?.isManaged ?? false) { - Section("Configure") { Picker("Configuring Node", selection: $selectedNode) { if selectedNode == 0 { @@ -91,10 +85,8 @@ struct Settings: View { let node = nodes.first(where: { $0.num == newValue }) let connectedNode = nodes.first(where: { $0.num == connectedNodeNum }) connectedNodeNum = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) - if connectedNode != nil && connectedNode?.user != nil && connectedNode?.myInfo != nil && node?.user != nil && node?.metadata == nil { let adminMessageId = bleManager.requestDeviceMetadata(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode!.myInfo!.adminIndex, context: context) - if adminMessageId > 0 { print("Sent node metadata request from node details") } @@ -103,7 +95,6 @@ struct Settings: View { } } Section("radio.configuration") { - NavigationLink { ShareChannels(node: nodes.first(where: { $0.num == connectedNodeNum })) } label: { @@ -113,17 +104,14 @@ struct Settings: View { } .tag(SettingsSidebar.shareChannels) .disabled(selectedNode > 0 && selectedNode != connectedNodeNum) - NavigationLink { UserConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { - Image(systemName: "person.crop.rectangle.fill") .symbolRenderingMode(.hierarchical) Text("user") } .tag(SettingsSidebar.userConfig) - NavigationLink { LoRaConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { @@ -132,7 +120,6 @@ struct Settings: View { Text("lora") } .tag(SettingsSidebar.loraConfig) - NavigationLink { Channels(node: nodes.first(where: { $0.num == connectedNodeNum })) } label: { @@ -142,7 +129,6 @@ struct Settings: View { } .tag(SettingsSidebar.channelConfig) .disabled(selectedNode > 0 && selectedNode != connectedNodeNum) - NavigationLink { BluetoothConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { @@ -151,7 +137,6 @@ struct Settings: View { Text("bluetooth") } .tag(SettingsSidebar.bluetoothConfig) - NavigationLink { DeviceConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { @@ -160,7 +145,6 @@ struct Settings: View { Text("device") } .tag(SettingsSidebar.deviceConfig) - NavigationLink { DisplayConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { @@ -169,52 +153,40 @@ struct Settings: View { Text("display") } .tag(SettingsSidebar.displayConfig) - NavigationLink { NetworkConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { - Image(systemName: "network") .symbolRenderingMode(.hierarchical) Text("network") } .tag(SettingsSidebar.networkConfig) - NavigationLink { PositionConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { - Image(systemName: "location") .symbolRenderingMode(.hierarchical) Text("position") } .tag(SettingsSidebar.positionConfig) - } Section("module.configuration") { - NavigationLink { CannedMessagesConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { - Image(systemName: "list.bullet.rectangle.fill") .symbolRenderingMode(.hierarchical) - Text("canned.messages") } .tag(SettingsSidebar.cannedMessagesConfig) - NavigationLink { DetectionSensorConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { - Image(systemName: "sensor") .symbolRenderingMode(.hierarchical) - Text("detection.sensor") } .tag(SettingsSidebar.detectionSensorConfig) - NavigationLink { ExternalNotificationConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { @@ -223,7 +195,6 @@ struct Settings: View { Text("external.notification") } .tag(SettingsSidebar.externalNotificationConfig) - NavigationLink { MQTTConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { @@ -232,7 +203,6 @@ struct Settings: View { Text("mqtt") } .tag(SettingsSidebar.mqttConfig) - NavigationLink { RangeTestConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { @@ -248,7 +218,6 @@ struct Settings: View { Text("ringtone") } .tag(SettingsSidebar.ringtoneConfig) - NavigationLink { SerialConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { @@ -257,7 +226,6 @@ struct Settings: View { Text("serial") } .tag(SettingsSidebar.serialConfig) - NavigationLink { StoreForwardConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { @@ -266,7 +234,6 @@ struct Settings: View { Text("storeforward") } .tag(SettingsSidebar.serialConfig) - NavigationLink { TelemetryConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { @@ -276,7 +243,6 @@ struct Settings: View { } .tag(SettingsSidebar.telemetryConfig) } - Section(header: Text("logging")) { NavigationLink { MeshLog() @@ -286,7 +252,6 @@ struct Settings: View { Text("mesh.log") } .tag(SettingsSidebar.meshLog) - NavigationLink { let connectedNode = nodes.first(where: { $0.num == connectedNodeNum }) AdminMessageList(user: connectedNode?.user) @@ -302,8 +267,7 @@ struct Settings: View { Firmware(node: nodes.first(where: { $0.num == connectedNodeNum })) } label: { Image(systemName: "arrow.up.arrow.down.square") - .symbolRenderingMode(.hierarchical) - + .symbolRenderingMode(.hierarchical) Text("Firmware Updates") } .tag(SettingsSidebar.about) diff --git a/Widgets/BatteryLevel.swift b/Widgets/BatteryLevel.swift index c9bfdccd..04816b3f 100644 --- a/Widgets/BatteryLevel.swift +++ b/Widgets/BatteryLevel.swift @@ -43,15 +43,12 @@ struct BatteryIcon: View { .font(font) .foregroundColor(color) .symbolRenderingMode(.hierarchical) - } else if batteryLevel! == 0 { - Image(systemName: "battery.0") .font(font) .foregroundColor(.red) .symbolRenderingMode(.hierarchical) } else if batteryLevel! > 100 { - Image(systemName: "powerplug") .font(font) .foregroundColor(color) diff --git a/Widgets/WidgetsLiveActivity.swift b/Widgets/WidgetsLiveActivity.swift index dd72d684..6b67b552 100644 --- a/Widgets/WidgetsLiveActivity.swift +++ b/Widgets/WidgetsLiveActivity.swift @@ -63,7 +63,7 @@ struct WidgetsLiveActivity: Widget { .tint(Color("LightIndigo")) } - DynamicIslandExpandedRegion(.bottom){ + DynamicIslandExpandedRegion(.bottom) { Text(context.attributes.name) .font(context.attributes.name.count > 14 ? .callout : .title3) .fontWeight(.semibold) From 4d26512dba25a1c4a8f41c943277b8eb4abca15f Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 27 Aug 2023 08:49:09 -0700 Subject: [PATCH 37/40] Create a my info when creating a nodeinfo --- Meshtastic/Persistence/UpdateCoreData.swift | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index d1008290..17b179e6 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -130,6 +130,20 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) newUser.hwModel = String(describing: newUserMessage.hwModel).uppercased() newNode.user = newUser } + + let myInfoEntity = MyInfoEntity(context: context) + myInfoEntity.myNodeNum = Int64(packet.from) + myInfoEntity.rebootCount = 0 + do { + try context.save() + print("💾 Saved a new myInfo for node number: \(String(packet.from))") + } catch { + context.rollback() + let nsError = error as NSError + print("💥 Error Inserting New Core Data MyInfoEntity: \(nsError)") + } + newNode.myInfo = myInfoEntity + } else { // Update an existing node fetchedNode[0].id = Int64(packet.from) From 7c1d694aadfc7427a0fcb65dfafd97df64721ebb Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 27 Aug 2023 08:54:38 -0700 Subject: [PATCH 38/40] send objectWillChange.send() when getting a new node. --- Meshtastic/Persistence/UpdateCoreData.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 17b179e6..72bf71a6 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -143,6 +143,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) print("💥 Error Inserting New Core Data MyInfoEntity: \(nsError)") } newNode.myInfo = myInfoEntity + newNode.objectWillChange.send() } else { // Update an existing node From 977df05887cb27fd96796dfad7dc5b55244de1f9 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 27 Aug 2023 09:17:23 -0700 Subject: [PATCH 39/40] Remove unused SwiftLint file --- Meshtastic.xcodeproj/project.pbxproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index a5e81a19..7866e58c 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -156,7 +156,6 @@ DDDEE5E129DA3E1100A8E078 /* NodeInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDEE5E029DA3E1100A8E078 /* NodeInfoView.swift */; }; DDE0F7C5295F77B700B8AAB3 /* AppSettingsEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */; }; DDF6B2482A9AEBF500BA6931 /* StoreForward.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF6B2472A9AEBF500BA6931 /* StoreForward.swift */; }; - DDF6B24A2A9B09A100BA6931 /* swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = DDF6B2492A9B09A100BA6931 /* swiftlint.yml */; }; DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF924C926FBB953009FE055 /* ConnectedDevice.swift */; }; DDFEB3BB29900C1200EE7472 /* CurrentConditionsCompact.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */; }; /* End PBXBuildFile section */ @@ -370,7 +369,6 @@ DDEE03EC29544A1000FCAD57 /* MeshtasticDataModelV4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV4.xcdatamodel; sourceTree = ""; }; DDF6B2462A9AEB9E00BA6931 /* MeshtasticDataModelV17.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV17.xcdatamodel; sourceTree = ""; }; DDF6B2472A9AEBF500BA6931 /* StoreForward.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreForward.swift; sourceTree = ""; }; - DDF6B2492A9B09A100BA6931 /* swiftlint.yml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.yaml; path = swiftlint.yml; sourceTree = ""; }; DDF924C926FBB953009FE055 /* ConnectedDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedDevice.swift; sourceTree = ""; }; DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentConditionsCompact.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -599,7 +597,6 @@ DDC2E14B26CE248E0042C5E4 = { isa = PBXGroup; children = ( - DDF6B2492A9B09A100BA6931 /* swiftlint.yml */, DDCDC6CD29481FCC004C1DDA /* Localizable.strings */, DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */, DDC2E15626CE248E0042C5E4 /* Meshtastic */, @@ -956,7 +953,6 @@ buildActionMask = 2147483647; files = ( DDC2E15F26CE248F0042C5E4 /* Preview Assets.xcassets in Resources */, - DDF6B24A2A9B09A100BA6931 /* swiftlint.yml in Resources */, DDCDC6CB29481FCC004C1DDA /* Localizable.strings in Resources */, DDDE5A1329AFEAB900490C6C /* Assets.xcassets in Resources */, DDB75A1A2A05EB67006ED576 /* alpha.png in Resources */, From 41c52a1f478469758aaeb4da4b1a35855b01704e Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 27 Aug 2023 18:29:27 -0700 Subject: [PATCH 40/40] Add polish language --- Meshtastic.xcodeproj/project.pbxproj | 3 + .../Settings/Config/Module/StoreForward.swift | 12 +- pl.lproj/Localizable.strings | 286 ++++++++++++++++++ 3 files changed, 296 insertions(+), 5 deletions(-) create mode 100644 pl.lproj/Localizable.strings diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 7866e58c..6ff8c283 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -369,6 +369,7 @@ DDEE03EC29544A1000FCAD57 /* MeshtasticDataModelV4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV4.xcdatamodel; sourceTree = ""; }; DDF6B2462A9AEB9E00BA6931 /* MeshtasticDataModelV17.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV17.xcdatamodel; sourceTree = ""; }; DDF6B2472A9AEBF500BA6931 /* StoreForward.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreForward.swift; sourceTree = ""; }; + DDF6B24B2A9C2FC800BA6931 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; DDF924C926FBB953009FE055 /* ConnectedDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedDevice.swift; sourceTree = ""; }; DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentConditionsCompact.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -928,6 +929,7 @@ de, Base, "zh-Hans", + pl, ); mainGroup = DDC2E14B26CE248E0042C5E4; packageReferences = ( @@ -1200,6 +1202,7 @@ DDCDC6CC29481FCC004C1DDA /* en */, DDCDC6CE294821AD004C1DDA /* de */, A65FA974296876BF00A97686 /* zh-Hans */, + DDF6B24B2A9C2FC800BA6931 /* pl */, ); name = Localizable.strings; sourceTree = ""; diff --git a/Meshtastic/Views/Settings/Config/Module/StoreForward.swift b/Meshtastic/Views/Settings/Config/Module/StoreForward.swift index d1e09b84..52845e7d 100644 --- a/Meshtastic/Views/Settings/Config/Module/StoreForward.swift +++ b/Meshtastic/Views/Settings/Config/Module/StoreForward.swift @@ -80,10 +80,12 @@ struct StoreForwardConfig: View { .pickerStyle(DefaultPickerStyle()) Picker("History Return Window", selection: $historyReturnWindow ) { Text("unset").tag(0) - Text("One Hour").tag(60) - Text("Two Hours").tag(120) - Text("Four Hours").tag(240) - Text("Six Hours").tag(360) + Text("One Minute").tag(60) + Text("Five Minutes").tag(300) + Text("Ten Minutes").tag(600) + Text("Fifteen Minutes").tag(900) + Text("Thirty Minutes").tag(1800) + Text("One Hour").tag(3600) } .pickerStyle(DefaultPickerStyle()) } @@ -178,7 +180,7 @@ struct StoreForwardConfig: View { self.heartbeat = (node?.storeForwardConfig?.heartbeat ?? true) self.records = Int(node?.storeForwardConfig?.records ?? 50) self.historyReturnMax = Int(node?.storeForwardConfig?.historyReturnMax ?? 100) - self.historyReturnWindow = Int(node?.storeForwardConfig?.historyReturnWindow ?? 60) + self.historyReturnWindow = Int(node?.storeForwardConfig?.historyReturnWindow ?? 300) self.hasChanges = false } } diff --git a/pl.lproj/Localizable.strings b/pl.lproj/Localizable.strings new file mode 100644 index 00000000..1f296093 --- /dev/null +++ b/pl.lproj/Localizable.strings @@ -0,0 +1,286 @@ +/* + Localizable.strings + Meshtastic + + Copyright(c) Garth Vander Houwen on 12/12/22. + +*/ +"about"="About"; +"about.meshtastic"="About Meshtastic"; +"admin"="Admin"; +"admin.log"="Admin Message Log"; +"ago"="ago"; +"airtime"="Airtime"; +"always.on"="Always On"; +"app.settings"="App Settings"; +"are.you.sure"="Are you sure?"; +"ascii.capable"="ASCII Capable"; +"available.radios"="Available Radios"; +"automatic.detection"="Automatic Detection"; +"battery.level"="Battery Level"; +"ble.name"="BLE Name"; +"ble.connection.timeout %d %@"="Connection failed after %d attempts to connect to %@. You may need to forget your device under Settings > Bluetooth."; +"ble.errorcode.6 %@"="%@ The app will automatically reconnect to the preferred radio if it comes back in range."; +"ble.errorcode.14 %@"="%@ This error usually cannot be fixed without forgetting the device unders Settings > Bluetooth and re-connecting to the radio."; +"ble.errorcode.pin %@"="%@ Please try connecting again and check the PIN carefully."; +"bluetooth"="Bluetooth"; +"bluetooth.off"="Bluetooth is off"; +"bluetooth.config"="Bluetooth Config"; +"bluetooth.mode.randompin"="Random PIN"; +"bluetooth.mode.fixedpin"="Fixed PIN"; +"bluetooth.mode.nopin"="No PIN (Just Works)"; +"bluetooth.pairingmode"="Pairing Mode"; +"bluetooth.pin.validation"="BLE Pin must be 6 digits long."; +"bytes"="Bytes"; +"cancel"="Cancel"; +"canned.messages"="Canned Messages"; +"canned.messages.config"="Canned Messages Config"; +"canned.messages.preset.manual"="Manual Configuration"; +"canned.messages.preset.rakrotary"="RAK Rotary Encoder Module"; +"canned.messages.preset.cardkb"="M5 Stack Card KB / RAK Keypad"; +"channel"="Channel"; +"channel.role.disabled"="Disabled"; +"channel.role.primary"="Primary"; +"channel.role.secondary"="Secondary"; +"channel.utilization"="Channel Utilization"; +"channels"="Channels"; +"clear.app.data"="Clear App Data"; +"clear.log"="Clear"; +"close"="Close"; +"config.save.confirm"="After config values save the node will reboot."; +"communicating"="Communicating with device. ."; +"connected.radio"="Connected Radio"; +"connected"="Connected"; +"connecting"="Connecting . ."; +"contacts"="Contacts"; +"copy"="Copy"; +"current"="Current"; +"default"="Default"; +"delete"="Delete"; +"detection.sensor"="Detection Sensor"; +"detection.sensor.config"="Detection Sensor Config"; +"detection.sensor.log"="Detection Sensor Log"; +"device"="Device"; +"device.config"="Device Config"; +"device.metrics.delete"="Delete all device metrics?"; +"device.metrics.log"="Device Metrics Log"; +"device.role.client"="Client (default) - App connected client."; +"device.role.clientmute"="Client Mute - Same as a client except packets will not hop over this node, does not contribute to routing packets for mesh."; +"device.role.router"="Router - Mesh packets will prefer to be routed over this node. Assumes device will operate in a standalone manner while placed in a location with a coverage advantage. WARNING: The BLE/Wi-Fi radios and the OLED screen will be put to sleep."; +"device.role.routerclient"="Router Client - Hybrid of the Client and Router roles. Similar to Router, except the Router Client can be used as both a Router and an app connected Client. BLE/Wi-Fi and OLED screen will not be put to sleep."; +"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"="Direct Messages"; +"dismiss.keyboard"="Dismiss"; +"display"="Display (Device Screen)"; +"display.config"="Display Config"; +"distance"="Distance"; +"disconnect"="Disconnect"; +"echo"="Echo"; +"email.address"="Email Address"; +"enabled"="Enabled"; +"encrypted"="Encrypted"; +"external.notification"="External Notification"; +"external.notification.config"="External Notification Config"; +"firmware.version"="Firmware Version"; +"firmware.version.unsupported"="Unsupported Firmware Version Detected, unable to connect to device."; +"gas"="Gas"; +"gas.resistance"="Gas Resistance"; +"generate.qr.code"="Generate QR Code"; +"gpsformat.dec"="Decimal Degrees Format"; +"gpsformat.dms"="Degrees Minutes Seconds"; +"gpsformat.utm"="Universal Transverse Mercator"; +"gpsformat.mgrs"="Military Grid Reference System"; +"gpsformat.olc"="Open Location Code (aka Plus Codes)"; +"gpsformat.osgr"="Ordnance Survey Grid Reference"; +"heard"="Heard"; +"heard.last"="Last Heard"; +"hybrid"="Hybrid"; +"hybrid.flyover"="Hybrid Flyover"; +"include"="Include"; +"inputevent.none"="None"; +"inputevent.up"="Up"; +"inputevent.down"="Down"; +"inputevent.left"="Left"; +"inputevent.right"="Right"; +"inputevent.select"="Select"; +"inputevent.back"="Back"; +"inputevent.cancel"="Cancel"; +"interval.one.second"="One Second"; +"interval.two.seconds"="Two Seconds"; +"interval.three.seconds"="Three Seconds"; +"interval.four.seconds"="Four Seconds"; +"interval.five.seconds"="Five Seconds"; +"interval.ten.seconds"="Ten Seconds"; +"interval.fifteen.seconds"="Fifteen Seconds"; +"interval.twenty.seconds"="Twenty Seconds"; +"interval.twentyfive.seconds"="Twenty Five Seconds"; +"interval.thirty.seconds"="Thirty Seconds"; +"interval.fortyfive.seconds"="Forty Five Seconds"; +"interval.one.minute"="One Minute"; +"interval.two.minutes"="Two Minutes"; +"interval.five.minutes"="Five Minutes"; +"interval.ten.minutes"="Ten Minutes"; +"interval.fifteen.minutes"="Fifteen Minutes"; +"interval.thirty.minutes"="Thirty Minutes"; +"interval.one.hour"="One Hour"; +"interval.two.hours"="Two Hours"; +"interval.three.hours"="Three Hours"; +"interval.four.hours"="Four Hours"; +"interval.five.hours"="Five Hours"; +"interval.six.hours"="Six Hours"; +"interval.twelve.hours"="Twelve Hours"; +"interval.eighteen.hours"="Eighteen Hours"; +"interval.twentyfour.hours"="Twenty Four Hours"; +"interval.thirtysix.hours"="Thirty Six Hours"; +"interval.fortyeight.hours"="Forty Eight Hours"; +"interval.seventytwo.hours"="Seventy Two Hours"; +"keyboard.type"="Keyboard Type"; +"logging"="Logging"; +"lora"="LoRa"; +"lora.config"="LoRa Config"; +"map"="Mesh Map"; +"map.type"="Default Type"; +"map.centering"="Centering Mode"; +"map.tiles.delete"="Delete All Map Tiles"; +"map.recentering"="Automatic Re-centering"; +"map.usertrackingmode"="User tracking mode"; +"map.usertrackingmode.follow"="Follow"; +"map.usertrackingmode.followwithheading"="Follow with heading"; +"map.usertrackingmode.none"="None"; +"mesh.live.activity"="Mesh Live Activity"; +"mesh.log"="Mesh Log"; +"mesh.log.bluetooth.config %@"="Bluetooth config received: %@"; +"mesh.log.cannedmessage.config %@"="Canned Message module config received: %@"; +"mesh.log.cannedmessages.messages.get %@"="Requested Canned Messages Module Messages for node: %@"; +"mesh.log.cannedmessages.messages.received %@"="Canned Messages Messages Received For: %@"; +"mesh.log.channel.sent %@ %d"="Sent a Channel for: %@ Channel Index %d"; +"mesh.log.channel.received %d %@"="Channel %d received from: %@"; +"mesh.log.device.config %@"="Device config received: %@"; +"mesh.log.display.config %@"="Display config received: %@"; +"mesh.log.devicemetadata %@"="Requesting Device Metadata for %@"; +"mesh.log.device.metadata.received %@"="Device Metadata received from: %@"; +"mesh.log.detectionsensor.config %@"="Detection Sensor module config received: %@"; +"mesh.log.externalnotification.config %@"="External Notifiation module config received: %@"; +"mesh.log.lora.config %@"="LoRa config received: %@"; +"mesh.log.lora.config.sent %@"="Sent a LoRa.Config for: %@"; +"mesh.log.mqtt.config %@"="MQTT module config received: %@"; +"mesh.log.myinfo %@"="MyInfo received: %@"; +"mesh.log.network.config %@"="Network config received: %@"; +"mesh.log.nodeinfo.received %@"="Node info received for: %@"; +"mesh.log.position.config %@"="Positon config received: %@"; +"mesh.log.position.received %@"="Position Packet received from node: %@"; +"mesh.log.rangetest.config %@"="Range Test module config received: %@"; +"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 %@"="Sent a Position Packet from the Apple device GPS to node: %@"; +"mesh.log.storeforward.config %@"="Store & Forward module config received: %@"; +"mesh.log.telemetry.config %@"="Telemetry module config received: %@"; +"mesh.log.telemetry.received %@"="Telemetry received for: %@"; +"mesh.log.textmessage.received"="Message received from the text message app."; +"mesh.log.textmessage.send.failed %@"="Message Send Failed, not properly connected to %@"; +"mesh.log.textmessage.sent %@ %@ %@"="Sent message %@ from %@ to %@"; +"mesh.log.traceroute.received.direct %@"="Trace Route request sent to node: %@ was recieived directly."; +"mesh.log.traceroute.received.route %@"="Trace Route request returned: %@"; +"mesh.log.traceroute.sent %@"="Sent a Trace Route Request to node: %@"; +"mesh.log.wantconfig %@"="Issuing Want Config to %@"; +"mesh.log.waypoint.sent %@"="Sent a Waypoint Packet from: %@"; +"mesh.log.waypoint.received %@"="Waypoint Packet received from node: %@"; +"message"="Message"; +"message.details"="Message Details"; +"messages"="Messages"; +"mode"="Mode"; +"module.configuration"="Module Configuration"; +"mqtt"="MQTT"; +"mqtt.config"="MQTT Config"; +"mqtt.clientproxy"="MQTT Client Proxy"; +"mqtt.username"="Username"; +"name"="Name"; +"network"="Network"; +"network.config"="Network Config"; +"nodes"="Nodes"; +"nodes %@"="Nodes (%@)"; +"no.nodes"="No Meshtastic Nodes Found"; +"not.connected"="No device connected"; +"numbers.punctuation"="Numbers and Punctuation"; +"off"="Off"; +"offline"="Offline"; +"on.boot"="On Boot Only"; +"options"="Options"; +"password"="Password"; +"phone.gps"="Phone GPS"; +"phone.gps.interval.description"="How frequently your phone will send your location to the device, location updates to the mesh are managed by the device."; +"position"="Position"; +"position.config"="Position Config"; +"preferred.radio"="Preferred Radio"; +"provide.location"="Share location"; +"radio.configuration"="Radio Configuration"; +"range.test"="Range Test"; +"range.test.config"="Range Test Config"; +"reply"="Reply"; +"reboot"="Reboot"; +"reboot.node"="Reboot node?"; +"received.ack"="Received Ack"; +"received.ack.real"="Recipient Ack"; +"ringtone"="Ringtone"; +"ringtone.config"="Ringtone Config"; +"routing.acknowledged"="Acknowledged"; +"routing.noroute"="No Route"; +"routing.gotnak"="Received a negative acknowledgment"; +"routing.timeout"="Timeout"; +"routing.nointerface"="No Interface"; +"routing.maxretransmit"="Max Retransmission Reached"; +"routing.nochannel"="No Channel"; +"routing.toolarge"="The packet is too large"; +"routing.noresponse"="No Response"; +"routing.dutycyclelimit"="Regional Duty Cycle Limit Reached"; +"routing.badRequest"="Bad Request"; +"routing.notauthorized"="Not Authorized"; +"satellite"="Satellite"; +"satellite.flyover"="Satellite Flyover"; +"save"="Save"; +"save.config %@"="Save Config for %@"; +"serial"="Serial"; +"serial.config"="Serial Config"; +"serial.mode.default"="Default"; +"serial.mode.simple"="Simple"; +"serial.mode.proto"="Protobufs"; +"serial.mode.txtmsg"="Text Message"; +"serial.mode.nmea"="NMEA Positions"; +"settings"="Settings"; +"share.channels"="Share Channels QR Code"; +"share.position"="Share Position"; +"subscribed"="Subscribed to mesh"; +"select.contact"="Select a Contact"; +"select.node"="Select a Node"; +"select.menu.item"="Select an item from the menu"; +"set.region"="Set LoRa Region"; +"standard"="Standard"; +"standard.muted"="Standard Muted"; +"storeforward"="Store & Forward"; +"storeforward.config"="Store & Forward Config"; +"storeforward.heartbeat"="Send Heartbeat"; +"ssid"="SSID"; +"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"; +"telemetry"="Telemetry (Sensors)"; +"telemetry.config"="Telemetry Config"; +"timeout"="Timeout"; +"timestamp"="Timestamp"; +"twitter"="Twitter"; +"unknown"="Unknown"; +"unknown.age"="Unknown Age"; +"unset"="Unset"; +"update.firmware"="Update Your Firmware"; +"update.interval"="Update Interval"; +"user"="User"; +"user.details"="User Details"; +"voltage"="Voltage"; +"waiting"="Waiting. . .";