From 3219ab6982d9028d78a2aa695021c03263455da8 Mon Sep 17 00:00:00 2001 From: Matthew Davies Date: Wed, 3 Apr 2024 14:52:41 -0700 Subject: [PATCH 01/13] Better mqtt status icons, including up/down icons --- Meshtastic/Views/Bluetooth/Connect.swift | 2 +- .../Views/Helpers/ConnectedDevice.swift | 80 ++++++++++--------- Meshtastic/Views/Helpers/MQTTIcon.swift | 54 +++++++++++++ .../Views/Messages/ChannelMessageList.swift | 8 +- .../Settings/Config/Module/MQTTConfig.swift | 2 +- 5 files changed, 103 insertions(+), 43 deletions(-) create mode 100644 Meshtastic/Views/Helpers/MQTTIcon.swift diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index 8fcc31cb..42d23865 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -260,7 +260,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.mqttProxyConnected) + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) } .sheet(isPresented: $invalidFirmwareVersion, onDismiss: didDismissSheet) { diff --git a/Meshtastic/Views/Helpers/ConnectedDevice.swift b/Meshtastic/Views/Helpers/ConnectedDevice.swift index 7e9d1852..6a33a970 100644 --- a/Meshtastic/Views/Helpers/ConnectedDevice.swift +++ b/Meshtastic/Views/Helpers/ConnectedDevice.swift @@ -5,56 +5,58 @@ A view draws the indicator used in the upper right corner for views using BLE import SwiftUI + struct ConnectedDevice: View { var bluetoothOn: Bool var deviceConnected: Bool var name: String - var mqttProxyEnabled: Bool = false - var mqttProxyConnected: Bool = false - var phoneOnly: Bool = false + + var mqttProxyConnected: Bool = false + var mqttUplinkEnabled: Bool = false + var mqttDownlinkEnabled: Bool = false + var mqttTopic: String = "" + var phoneOnly: Bool = false var body: some View { - HStack { - - if (phoneOnly && UIDevice.current.userInterfaceIdiom == .phone) || !phoneOnly { - if bluetoothOn { - if deviceConnected && (mqttProxyEnabled || mqttProxyConnected) { - if (mqttProxyConnected || mqttProxyEnabled) { - Image(systemName: "iphone.gen3.radiowaves.left.and.right.circle.fill") - .imageScale(.large) - .foregroundColor(mqttProxyConnected ? .green : .gray) - .symbolRenderingMode(.hierarchical) - } - } - if deviceConnected { - Image(systemName: "antenna.radiowaves.left.and.right.circle.fill") - .imageScale(.large) - .foregroundColor(.green) - .symbolRenderingMode(.hierarchical) - Text(name).font(name.isEmoji() ? .title : .callout).foregroundColor(.gray) - } else { - - Image(systemName: "antenna.radiowaves.left.and.right.slash") - .imageScale(.medium) - .foregroundColor(.red) - .symbolRenderingMode(.hierarchical) - } - } else { - Text("bluetooth.off").font(.subheadline).foregroundColor(.red) - } - } + if (phoneOnly && UIDevice.current.userInterfaceIdiom == .phone) || !phoneOnly { + if bluetoothOn { + if deviceConnected { + MQTTIcon(connected: mqttProxyConnected, uplink: mqttUplinkEnabled, downlink: mqttDownlinkEnabled, topic: mqttTopic) + Image(systemName: "antenna.radiowaves.left.and.right.circle.fill") + .imageScale(.large) + .foregroundColor(.green) + .symbolRenderingMode(.hierarchical) + Text(name).font(name.isEmoji() ? .title : .callout).foregroundColor(.gray) + } else { + Image(systemName: "antenna.radiowaves.left.and.right.slash") + .imageScale(.medium) + .foregroundColor(.red) + .symbolRenderingMode(.hierarchical) + } + } else { + Text("bluetooth.off").font(.subheadline).foregroundColor(.red) + } + } } } } + + + struct ConnectedDevice_Previews: PreviewProvider { static var previews: some View { - ConnectedDevice(bluetoothOn: true, deviceConnected: true, name: "MEMO", mqttProxyConnected: true) - .previewLayout(.fixed(width: 80, height: 70)) - - ConnectedDevice(bluetoothOn: true, deviceConnected: false, name: "86D4", mqttProxyConnected: false) - .previewLayout(.fixed(width: 80, height: 70)) - } - + VStack (alignment: .trailing) { + ConnectedDevice(bluetoothOn: true, deviceConnected: true, name: "MEMO", mqttProxyConnected: true) + ConnectedDevice(bluetoothOn: true, deviceConnected: true, name: "MEMO", mqttProxyConnected: false, mqttUplinkEnabled: true, mqttDownlinkEnabled: true) + ConnectedDevice(bluetoothOn: true, deviceConnected: true, name: "MEMO", mqttProxyConnected: true, mqttUplinkEnabled: true, mqttDownlinkEnabled: true, mqttTopic: "msh/US/2/e/#") + ConnectedDevice(bluetoothOn: true, deviceConnected: true, name: "MEMO", mqttProxyConnected: false, mqttUplinkEnabled: true, mqttDownlinkEnabled: false) + ConnectedDevice(bluetoothOn: true, deviceConnected: true, name: "MEMO", mqttProxyConnected: true, mqttUplinkEnabled: true, mqttDownlinkEnabled: false) + ConnectedDevice(bluetoothOn: true, deviceConnected: true, name: "MEMO", mqttProxyConnected: false, mqttUplinkEnabled: false, mqttDownlinkEnabled: true) + ConnectedDevice(bluetoothOn: true, deviceConnected: true, name: "MEMO", mqttProxyConnected: true, mqttUplinkEnabled: false, mqttDownlinkEnabled: true) + ConnectedDevice(bluetoothOn: true, deviceConnected: true, name: "MEMO", mqttProxyConnected: true) + ConnectedDevice(bluetoothOn: true, deviceConnected: false, name: "MEMO", mqttProxyConnected: false) + }.previewLayout(.fixed(width: 150, height: 275)) + } } diff --git a/Meshtastic/Views/Helpers/MQTTIcon.swift b/Meshtastic/Views/Helpers/MQTTIcon.swift new file mode 100644 index 00000000..f67887e0 --- /dev/null +++ b/Meshtastic/Views/Helpers/MQTTIcon.swift @@ -0,0 +1,54 @@ +// +// MQTTIcon.swift +// Meshtastic +// +// Created by Matthew Davies on 4/1/24. +// + +import Foundation +import SwiftUI + +struct MQTTIcon: View { + var connected: Bool = false + var uplink: Bool = false + var downlink: Bool = false + var topic: String = "" + + @State var isPopoverOpen = false + + var body: some View { + Button( action: { + if(topic.length > 0) {self.isPopoverOpen.toggle()} + } ) { + // the last one defaults to just showing up/down if it isn't specified b/c on the mqtt config screen, there's no information about uplink/downlink and no good alternative icon + Image(systemName: uplink && downlink ? "arrow.up.arrow.down.circle.fill" : uplink ? "arrow.up.circle.fill" : downlink ? "arrow.down.circle.fill" : "arrow.up.arrow.down.circle.fill") + .imageScale(.large) + .foregroundColor(connected ? .green : .gray) + .symbolRenderingMode(.hierarchical) + }.popover(isPresented: self.$isPopoverOpen, content: { + VStack(spacing: 0.5) { + Text("Subscribed to topic: " + topic) + .padding(20) + Button("Close", action: { self.isPopoverOpen = false }).padding([.bottom], 20) + } + }) + } +} + +struct MQTTIcon_Previews: PreviewProvider { + static var previews: some View { + VStack { + MQTTIcon(connected: true) + MQTTIcon(connected: false) + + MQTTIcon(connected: true, uplink: true, downlink: true) + MQTTIcon(connected: false, uplink: true, downlink: true) + + MQTTIcon(connected: true, uplink: true) + MQTTIcon(connected: false, uplink: true) + + MQTTIcon(connected: true, downlink: true) + MQTTIcon(connected: false, downlink: true) + }.previewLayout(.fixed(width: 25, height: 220)) + } +} diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index 234d705a..9017951f 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -157,8 +157,12 @@ struct ChannelMessageList: View { bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?", - mqttProxyEnabled: channel.uplinkEnabled || channel.downlinkEnabled, - mqttProxyConnected: channel.uplinkEnabled || channel.downlinkEnabled ? bleManager.mqttProxyConnected : false + + // mqttProxyConnected defaults to false, so if it's not enabled it will still be false + mqttProxyConnected: bleManager.mqttProxyConnected, + mqttUplinkEnabled: channel.uplinkEnabled, + mqttDownlinkEnabled: channel.downlinkEnabled, + mqttTopic: bleManager.mqttManager.topic ) } } diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index b0de0cbb..be067293 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -289,7 +289,7 @@ struct MQTTConfig: View { .navigationTitle("mqtt.config") .navigationBarItems(trailing: ZStack { - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?", mqttProxyEnabled: self.enabled, mqttProxyConnected: bleManager.mqttProxyConnected) + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?", mqttProxyConnected: bleManager.mqttProxyConnected) }) .onChange(of: address) { newAddress in if node != nil && node?.mqttConfig != nil { From aa08f2ff3320bb8f90bd7f4033d9f647ea754782 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 4 Apr 2024 09:39:03 -0700 Subject: [PATCH 02/13] Drop the last character for string length validation, bump version --- Meshtastic.xcodeproj/project.pbxproj | 8 ++--- .../Views/MapKitMap/WaypointFormMapKit.swift | 12 ++----- .../TextMessageField/TextMessageField.swift | 8 +---- .../Nodes/Helpers/Map/WaypointForm.swift | 12 ++----- .../Views/Settings/Channels/ChannelForm.swift | 6 +--- .../Config/Module/CannedMessagesConfig.swift | 7 +---- .../Config/Module/DetectionSensorConfig.swift | 7 +---- .../Settings/Config/Module/MQTTConfig.swift | 31 +++---------------- .../Settings/Config/Module/RtttlConfig.swift | 7 +---- .../Views/Settings/Config/NetworkConfig.swift | 12 ++----- Meshtastic/Views/Settings/UserConfig.swift | 12 ++----- 11 files changed, 21 insertions(+), 101 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 000b7ec6..f1cd50d1 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1611,7 +1611,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.3.3; + MARKETING_VERSION = 2.3.4; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1645,7 +1645,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.3.3; + MARKETING_VERSION = 2.3.4; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1767,7 +1767,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.3.3; + MARKETING_VERSION = 2.3.4; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1800,7 +1800,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.3.3; + MARKETING_VERSION = 2.3.4; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/Meshtastic/Views/MapKitMap/WaypointFormMapKit.swift b/Meshtastic/Views/MapKitMap/WaypointFormMapKit.swift index 722a95d8..f32300ea 100644 --- a/Meshtastic/Views/MapKitMap/WaypointFormMapKit.swift +++ b/Meshtastic/Views/MapKitMap/WaypointFormMapKit.swift @@ -53,11 +53,7 @@ struct WaypointFormMapKit: View { let totalBytes = name.utf8.count // Only mess with the value if it is too big if totalBytes > 30 { - let firstNBytes = Data(name.utf8.prefix(30)) - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - // Set the name back to the last place where it was the right size - name = maxBytesString - } + name = String(name.dropLast()) } }) } @@ -74,11 +70,7 @@ struct WaypointFormMapKit: View { let totalBytes = description.utf8.count // Only mess with the value if it is too big if totalBytes > 100 { - let firstNBytes = Data(description.utf8.prefix(100)) - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - // Set the name back to the last place where it was the right size - description = maxBytesString - } + description = String(description.dropLast()) } }) } diff --git a/Meshtastic/Views/Messages/TextMessageField/TextMessageField.swift b/Meshtastic/Views/Messages/TextMessageField/TextMessageField.swift index e5cd01d3..c8f1abaa 100644 --- a/Meshtastic/Views/Messages/TextMessageField/TextMessageField.swift +++ b/Meshtastic/Views/Messages/TextMessageField/TextMessageField.swift @@ -33,13 +33,7 @@ struct TextMessageField: View { totalBytes = value.utf8.count // Only mess with the value if it is too big if totalBytes > Self.maxbytes { - let firstNBytes = Data(typingMessage.utf8.prefix(Self.maxbytes)) - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - // Set the message back to the last place where it was the right size - typingMessage = maxBytesString - } else { - print("not a valid UTF-8 sequence") - } + typingMessage = String(typingMessage.dropLast()) } }) .keyboardType(.default) diff --git a/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift b/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift index 7ab6c7a6..446ed2c0 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift @@ -63,11 +63,7 @@ struct WaypointForm: View { let totalBytes = name.utf8.count // Only mess with the value if it is too big if totalBytes > 30 { - let firstNBytes = Data(name.utf8.prefix(30)) - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - // Set the name back to the last place where it was the right size - name = maxBytesString - } + name = String(name.dropLast()) } }) } @@ -84,11 +80,7 @@ struct WaypointForm: View { let totalBytes = description.utf8.count // Only mess with the value if it is too big if totalBytes > 100 { - let firstNBytes = Data(description.utf8.prefix(100)) - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - // Set the name back to the last place where it was the right size - description = maxBytesString - } + description = String(description.dropLast()) } }) } diff --git a/Meshtastic/Views/Settings/Channels/ChannelForm.swift b/Meshtastic/Views/Settings/Channels/ChannelForm.swift index 09238d49..cdde8442 100644 --- a/Meshtastic/Views/Settings/Channels/ChannelForm.swift +++ b/Meshtastic/Views/Settings/Channels/ChannelForm.swift @@ -47,11 +47,7 @@ struct ChannelForm: View { let totalBytes = channelName.utf8.count // Only mess with the value if it is too big if totalBytes > 11 { - let firstNBytes = Data(channelName.utf8.prefix(11)) - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - // Set the channelName back to the last place where it was the right size - channelName = maxBytesString - } + channelName = String(channelName.dropLast()) } hasChanges = true }) diff --git a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift index 67b45e3f..dd5df775 100644 --- a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift @@ -74,12 +74,7 @@ struct CannedMessagesConfig: View { let totalBytes = messages.utf8.count // Only mess with the value if it is too big if totalBytes > 198 { - - let firstNBytes = Data(messages.utf8.prefix(198)) - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - // Set the shortName back to the last place where it was the right size - messages = maxBytesString - } + messages = String(messages.dropLast()) } hasMessagesChanges = true }) diff --git a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift index bd00c76e..e78a9967 100644 --- a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift @@ -94,12 +94,7 @@ struct DetectionSensorConfig: View { 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 - } + name = String(name.dropLast()) } }) } diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index b0de0cbb..4175c623 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -139,11 +139,7 @@ struct MQTTConfig: View { let totalBytes = root.utf8.count // Only mess with the value if it is too big if totalBytes > 30 { - let firstNBytes = Data(root.utf8.prefix(30)) - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - // Set the shortName back to the last place where it was the right size - root = maxBytesString - } + root = String(root.dropLast()) } }) .foregroundColor(.gray) @@ -181,11 +177,7 @@ struct MQTTConfig: View { let totalBytes = address.utf8.count // Only mess with the value if it is too big if totalBytes > 62 { - let firstNBytes = Data(username.utf8.prefix(62)) - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - // Set the shortName back to the last place where it was the right size - address = maxBytesString - } + address = String(address.dropLast()) } hasChanges = true }) @@ -205,14 +197,7 @@ struct MQTTConfig: View { // Only mess with the value if it is too big if totalBytes > 62 { - - let firstNBytes = Data(username.utf8.prefix(62)) - - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - - // Set the shortName back to the last place where it was the right size - username = maxBytesString - } + username = String(username.dropLast()) } hasChanges = true }) @@ -229,17 +214,9 @@ struct MQTTConfig: View { .onChange(of: password, perform: { _ in let totalBytes = password.utf8.count - // Only mess with the value if it is too big if totalBytes > 62 { - - let firstNBytes = Data(password.utf8.prefix(62)) - - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - - // Set the shortName back to the last place where it was the right size - password = maxBytesString - } + password = String(password.dropLast()) } hasChanges = true }) diff --git a/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift b/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift index 78dd2b7e..dfd951df 100644 --- a/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift @@ -35,12 +35,7 @@ struct RtttlConfig: View { let totalBytes = ringtone.utf8.count // Only mess with the value if it is too big if totalBytes > 228 { - - let firstNBytes = Data(ringtone.utf8.prefix(228)) - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - // Set the ringtone back to the last place where it was the right size - ringtone = maxBytesString - } + ringtone = String(ringtone.dropLast()) } }) .foregroundColor(.gray) diff --git a/Meshtastic/Views/Settings/Config/NetworkConfig.swift b/Meshtastic/Views/Settings/Config/NetworkConfig.swift index 93923232..ca134b7b 100644 --- a/Meshtastic/Views/Settings/Config/NetworkConfig.swift +++ b/Meshtastic/Views/Settings/Config/NetworkConfig.swift @@ -48,11 +48,7 @@ struct NetworkConfig: View { let totalBytes = wifiSsid.utf8.count // Only mess with the value if it is too big if totalBytes > 32 { - let firstNBytes = Data(wifiSsid.utf8.prefix(32)) - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - // Set the shortName back to the last place where it was the right size - wifiSsid = maxBytesString - } + wifiSsid = String(wifiSsid.dropLast()) } hasChanges = true }) @@ -69,11 +65,7 @@ struct NetworkConfig: View { let totalBytes = wifiPsk.utf8.count // Only mess with the value if it is too big if totalBytes > 63 { - let firstNBytes = Data(wifiPsk.utf8.prefix(63)) - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - // Set the shortName back to the last place where it was the right size - wifiPsk = maxBytesString - } + wifiPsk = String(wifiPsk.dropLast()) } hasChanges = true }) diff --git a/Meshtastic/Views/Settings/UserConfig.swift b/Meshtastic/Views/Settings/UserConfig.swift index e7b71c60..a9da310d 100644 --- a/Meshtastic/Views/Settings/UserConfig.swift +++ b/Meshtastic/Views/Settings/UserConfig.swift @@ -52,11 +52,7 @@ struct UserConfig: View { let totalBytes = longName.utf8.count // Only mess with the value if it is too big if totalBytes > (isLicensed ? 6 : 36) { - let firstNBytes = Data(longName.utf8.prefix(isLicensed ? 6 : 36)) - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - // Set the longName back to the last place where it was the right size - longName = maxBytesString - } + longName = String(longName.dropLast()) } }) } @@ -80,11 +76,7 @@ struct UserConfig: View { let totalBytes = shortName.utf8.count // Only mess with the value if it is too big if totalBytes > 4 { - let firstNBytes = Data(shortName.utf8.prefix(4)) - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - // Set the shortName back to the last place where it was the right size - shortName = maxBytesString - } + shortName = String(shortName.dropLast()) } }) .foregroundColor(.gray) From 1e6546ea72ebf61614ede84ca5228f7e12d1cb9d Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 4 Apr 2024 15:25:02 -0700 Subject: [PATCH 03/13] New share channels tip --- Meshtastic/Views/Settings/ShareChannels.swift | 6 +++--- en.lproj/Localizable.strings | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Meshtastic/Views/Settings/ShareChannels.swift b/Meshtastic/Views/Settings/ShareChannels.swift index 627b7874..43a89d47 100644 --- a/Meshtastic/Views/Settings/ShareChannels.swift +++ b/Meshtastic/Views/Settings/ShareChannels.swift @@ -54,9 +54,9 @@ struct ShareChannels: View { var body: some View { if #available(iOS 17.0, macOS 14.0, *) { -// VStack { -// TipView(ShareChannelsTip(), arrowEdge: .bottom) -// } + VStack { + TipView(ShareChannelsTip(), arrowEdge: .bottom) + } } GeometryReader { bounds in let smallest = min(bounds.size.width, bounds.size.height) diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index b395eb99..28c468d2 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -341,7 +341,7 @@ "tip.channels.create.title"="Manage Channels"; "tip.channels.create.message"="Most data on your mesh is sent over the primary channel. You can set up secondary channels to create additional messaging groups secured by their own key. [Channel config tips](https://meshtastic.org/docs/configuration/tips/)"; "tip.channels.share.title"="Sharing Meshtastic Channels"; -"tip.channels.share.message"="A Meshtastic QR code contains the LoRa config and channel values needed to communicate. Most mesh activity takes place on the required Primary channel. If you don't share your primary channel your first shared channel becomes the primary channel on the other network. Other channels are for private groups, each with its own key."; +"tip.channels.share.message"="A Meshtastic QR code contains the LoRa config and channel values needed for radios to communicate. You can share a complete channel configuration using the Replace Channels option, if you choose Add Channels your shared channels will be added to the channels on the receiving radio."; "tip.messages.title"="Messages"; "tip.messages.message"="You can send and receive channel (group chats) and direct messages. From any message you can long press to see available actions like copy, reply, tapback and delete as well as delivery details."; "twitter"="Twitter"; From b5e621df89169dcf76b8c65f9c89e2b6d5b4dc53 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 5 Apr 2024 17:14:04 -0700 Subject: [PATCH 04/13] Show mqtt errors for the client proxy --- Meshtastic/Helpers/BLEManager.swift | 3 +++ Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift | 6 ++++++ Meshtastic/Views/Settings/ShareChannels.swift | 8 ++++---- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index e234cd35..a47384d3 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -27,6 +27,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate @Published var isSwitchedOn: Bool = false @Published var automaticallyReconnect: Bool = true @Published var mqttProxyConnected: Bool = false + @Published var mqttError: String = "" @StateObject var appState = AppState.shared public var minimumVersion = "2.0.0" public var connectedVersion: String @@ -312,6 +313,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate // MARK: MqttClientProxyManagerDelegate Methods func onMqttConnected() { mqttProxyConnected = true + mqttError = "" print("đŸ“Č Mqtt Client Proxy onMqttConnected now subscribing to \(mqttManager.topic).") mqttManager.mqttClientProxy?.subscribe(mqttManager.topic) } @@ -344,6 +346,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate func onMqttError(message: String) { mqttProxyConnected = false + mqttError = message print("đŸ“Č Mqtt Client Proxy onMqttError: \(message)") } diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index 4175c623..9079d530 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -67,6 +67,12 @@ struct MQTTConfig: View { if enabled && proxyToClientEnabled && node!.mqttConfig!.proxyToClientEnabled == true { Toggle(isOn: $mqttConnected) { Label(mqttConnected ? "mqtt.disconnect".localized : "mqtt.connect".localized, systemImage: "server.rack") + if bleManager.mqttError.count > 0 { + Text(bleManager.mqttError) + .fixedSize(horizontal: false, vertical: true) + .foregroundColor(.red) + } + } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } diff --git a/Meshtastic/Views/Settings/ShareChannels.swift b/Meshtastic/Views/Settings/ShareChannels.swift index 43a89d47..686af46f 100644 --- a/Meshtastic/Views/Settings/ShareChannels.swift +++ b/Meshtastic/Views/Settings/ShareChannels.swift @@ -219,10 +219,10 @@ struct ShareChannels: View { .resizable() .scaledToFit() .frame( - minWidth: smallest * (UIDevice.current.userInterfaceIdiom == .phone ? 0.9 : 0.6), - maxWidth: smallest * (UIDevice.current.userInterfaceIdiom == .phone ? 0.9 : 0.6), - minHeight: smallest * (UIDevice.current.userInterfaceIdiom == .phone ? 0.9 : 0.6), - maxHeight: smallest * (UIDevice.current.userInterfaceIdiom == .phone ? 0.9 : 0.6), + minWidth: smallest * (UIDevice.current.userInterfaceIdiom == .phone ? 0.8 : 0.6), + maxWidth: smallest * (UIDevice.current.userInterfaceIdiom == .phone ? 0.8 : 0.6), + minHeight: smallest * (UIDevice.current.userInterfaceIdiom == .phone ? 0.8 : 0.6), + maxHeight: smallest * (UIDevice.current.userInterfaceIdiom == .phone ? 0.8 : 0.6), alignment: .top ) } From 2ac6d80dab939ad62ad0fe0aa1f18fe1ab16675c Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 5 Apr 2024 17:29:37 -0700 Subject: [PATCH 05/13] Show admin channel in channels list --- Meshtastic/Views/Messages/ChannelList.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Meshtastic/Views/Messages/ChannelList.swift b/Meshtastic/Views/Messages/ChannelList.swift index 7ed6fb04..2c3a83f4 100644 --- a/Meshtastic/Views/Messages/ChannelList.swift +++ b/Meshtastic/Views/Messages/ChannelList.swift @@ -21,7 +21,7 @@ struct ChannelList: View { @State private var isPresentingTraceRouteSentAlert = false - var restrictedChannels = ["admin", "gpio", "mqtt", "serial"] + var restrictedChannels = ["gpio", "mqtt", "serial"] var body: some View { let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMdd", options: 0, locale: Locale.current) From 132b6f41ff22d0600b8721ca71fad2f854bd979f Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 6 Apr 2024 08:42:35 -0700 Subject: [PATCH 06/13] Add missing file to project --- Meshtastic.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index f1cd50d1..4017318d 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -201,6 +201,7 @@ DDE5B4042B2279A700FCDD05 /* TraceRouteLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE5B4032B2279A700FCDD05 /* TraceRouteLog.swift */; }; DDE5B4062B227E3200FCDD05 /* TraceRouteEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE5B4052B227E3200FCDD05 /* TraceRouteEntityExtension.swift */; }; DDE9659C2B1C3B6A00531070 /* RouteRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE9659B2B1C3B6A00531070 /* RouteRecorder.swift */; }; + DDF45C342BC1A48E005ED5F2 /* MQTTIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF45C332BC1A48E005ED5F2 /* MQTTIcon.swift */; }; DDF6B2482A9AEBF500BA6931 /* StoreForwardConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF6B2472A9AEBF500BA6931 /* StoreForwardConfig.swift */; }; DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF924C926FBB953009FE055 /* ConnectedDevice.swift */; }; DDFEB3BB29900C1200EE7472 /* CurrentConditionsCompact.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */; }; @@ -477,6 +478,7 @@ DDE5B4052B227E3200FCDD05 /* TraceRouteEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceRouteEntityExtension.swift; sourceTree = ""; }; DDE9659B2B1C3B6A00531070 /* RouteRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteRecorder.swift; sourceTree = ""; }; DDEE03EC29544A1000FCAD57 /* MeshtasticDataModelV4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV4.xcdatamodel; sourceTree = ""; }; + DDF45C332BC1A48E005ED5F2 /* MQTTIcon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MQTTIcon.swift; sourceTree = ""; }; DDF6B2462A9AEB9E00BA6931 /* MeshtasticDataModelV17.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV17.xcdatamodel; sourceTree = ""; }; DDF6B2472A9AEBF500BA6931 /* StoreForwardConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreForwardConfig.swift; sourceTree = ""; }; DDF6B24B2A9C2FC800BA6931 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; @@ -907,6 +909,7 @@ DDC2E18D26CE25CB0042C5E4 /* Helpers */ = { isa = PBXGroup; children = ( + DDF45C332BC1A48E005ED5F2 /* MQTTIcon.swift */, DD5E523D298F5A7D00D21B61 /* Weather */, DD47E3D526F17ED900029299 /* CircleText.swift */, DDF924C926FBB953009FE055 /* ConnectedDevice.swift */, @@ -1283,6 +1286,7 @@ 6DEDA55C2A9592F900321D2E /* MessageEntityExtension.swift in Sources */, DDDB444229F8A88700EE2349 /* Double.swift in Sources */, DD5E520F298EE33B00D21B61 /* cannedmessages.pb.swift in Sources */, + DDF45C342BC1A48E005ED5F2 /* MQTTIcon.swift in Sources */, DDB75A232A13CDA9006ED576 /* BatteryLevelCompact.swift in Sources */, DDB75A162A0594AD006ED576 /* TileOverlay.swift in Sources */, DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */, From 70d4c6043e0d84cc1027068be4cf7477b70166af Mon Sep 17 00:00:00 2001 From: Matthew Davies Date: Sat, 6 Apr 2024 16:02:25 -0700 Subject: [PATCH 07/13] Fix bug where MQTT was showing connected even if down/up was not enabled --- Meshtastic/Views/Messages/ChannelMessageList.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index 9017951f..8688eed4 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -159,7 +159,7 @@ struct ChannelMessageList: View { name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?", // mqttProxyConnected defaults to false, so if it's not enabled it will still be false - mqttProxyConnected: bleManager.mqttProxyConnected, + mqttProxyConnected: bleManager.mqttProxyConnected && (channel.uplinkEnabled || channel.downlinkEnabled), mqttUplinkEnabled: channel.uplinkEnabled, mqttDownlinkEnabled: channel.downlinkEnabled, mqttTopic: bleManager.mqttManager.topic From f40f9345516265d7925c9c2fca6242833f79f1cb Mon Sep 17 00:00:00 2001 From: Matthew Davies Date: Sat, 6 Apr 2024 16:09:08 -0700 Subject: [PATCH 08/13] Add mqtt connected indicator & topic to connect screen --- Meshtastic/Views/Bluetooth/Connect.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index 42d23865..cbade213 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -260,7 +260,7 @@ struct Connect: View { .navigationTitle("bluetooth") .navigationBarItems(leading: MeshtasticLogo(), trailing: ZStack { - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?", mqttProxyConnected: bleManager.mqttProxyConnected, mqttTopic: bleManager.mqttManager.topic) }) } .sheet(isPresented: $invalidFirmwareVersion, onDismiss: didDismissSheet) { From b449867f713cb82e9f93e659cc112affbecf1102 Mon Sep 17 00:00:00 2001 From: Lerold Date: Mon, 8 Apr 2024 10:30:40 +0200 Subject: [PATCH 09/13] Added Swedish Language files. --- se.lproj/Localizable.strings | 356 +++++++++++++++++++++++++++++++++++ 1 file changed, 356 insertions(+) create mode 100644 se.lproj/Localizable.strings diff --git a/se.lproj/Localizable.strings b/se.lproj/Localizable.strings new file mode 100644 index 00000000..00febe5d --- /dev/null +++ b/se.lproj/Localizable.strings @@ -0,0 +1,356 @@ +/* + Localizable.strings + Meshtastic + + Copyright(c) Garth Vander Houwen on 12/12/22. + +*/ +"about"="Om"; +"about.meshtastic"="Om Meshtastic"; +"admin"="Administratör"; +"admin.log"="Administratörsmeddelandelogg"; +"ago"="sedan"; +"airtime"="SĂ€ndningstid"; +"always.on"="Alltid pĂ„"; +"ambient.lighting"="Omgivningsbelysning"; +"ambient.lighting.config"="Konfiguration av omgivningsbelysning"; +"appsettings"="AppinstĂ€llningar"; +"appsettings.provide.location"="Dela plats"; +"appsettings.smartposition"="Smart position"; +"are.you.sure"="Är du sĂ€ker?"; +"ascii.capable"="ASCII-kompatibel"; +"available.radios"="TillgĂ€ngliga radioapparater"; +"automatic.detection"="Automatisk upptĂ€ckt"; +"battery.level"="BatterinivĂ„"; +"ble.name"="BLE-namn"; +"ble.connection.timeout %d %@"="Anslutningen misslyckades efter %d försök att ansluta till %@. Du kan behöva glömma din enhet under InstĂ€llningar > Bluetooth."; +"ble.errorcode.6 %@"="%@ Appen kommer automatiskt att Ă„teransluta till den föredragna radion om den kommer inom rĂ€ckhĂ„ll igen."; +"ble.errorcode.14 %@"="%@ Detta fel kan vanligtvis inte Ă„tgĂ€rdas utan att glömma enheten under InstĂ€llningar > Bluetooth och Ă„teransluta till radion."; +"ble.errorcode.pin %@"="%@ Försök att ansluta igen och kontrollera PIN-koden noggrant."; +"bluetooth"="Bluetooth"; +"bluetooth.off"="Bluetooth Ă€r avstĂ€ngt"; +"bluetooth.config"="Bluetooth-konfiguration"; +"bluetooth.mode.randompin"="SlumpmĂ€ssig PIN"; +"bluetooth.mode.fixedpin"="Fast PIN"; +"bluetooth.mode.nopin"="Ingen PIN (Bara fungerar)"; +"bluetooth.pairingmode"="ParlĂ€ge"; +"bluetooth.pin.validation"="BLE-PIN mĂ„ste vara 6 siffror lĂ„ng."; +"bytes"="Bytes"; +"cancel"="Avbryt"; +"canned.messages"="Fördefinierade meddelanden"; +"canned.messages.config"="Konfiguration av fördefinierade meddelanden"; +"canned.messages.preset.manual"="Manuell konfiguration"; +"canned.messages.preset.rakrotary"="RAK Rotary Encoder-modul"; +"canned.messages.preset.cardkb"="M5 Stack Card KB / RAK Keypad"; +"channel"="Kanal"; +"channel.role.disabled"="Inaktiverad"; +"channel.role.primary"="PrimĂ€r"; +"channel.role.secondary"="SekundĂ€r"; +"channel.utilization"="Kanalutnyttjande"; +"channels"="Kanaler"; +"clear.app.data"="Rensa appdata"; +"clear.log"="Rensa"; +"close"="StĂ€ng"; +"config.power.settings"="Ström"; +"config.power.title"="Strömkonfiguration"; +"config.power.section.battery"="Batteri"; +"config.power.section.sleep"="Sömn"; +"config.power.adc.override"="ADC-överskrivning"; +"config.power.adc.multiplier"="Multiplikator"; +"config.power.ls.secs"="Intervall för Ljussömn"; +"config.power.min.wake.secs"="Minsta VĂ€ckningsintervall"; +"config.power.saving"="StrömsparlĂ€ge"; +"config.power.saving.description"="SĂ€tter allt i vilolĂ€ge sĂ„ mycket som möjligt, för spĂ„rnings- och sensorlĂ€ge kommer detta ocksĂ„ inkludera LoRa-radion. AnvĂ€nd inte denna instĂ€llning om du vill anvĂ€nda din enhet med mobilappar eller anvĂ€nder en enhet utan en anvĂ€ndarknapp."; +"config.power.shutdown.on.power.loss"="StĂ€ng av vid Strömförlust"; +"config.power.shutdown.after.secs"="Efter"; +"config.power.wait.bluetooth.secs"="Bluetooth StĂ€ngs Av Efter"; +"config.ringtone"="RTTTL Ringsignal"; +"config.ringtone.title"="Ringsignalskonfiguration"; +"config.ringtone.label"="SprĂ„k för Överföring av Ringsignal"; +"config.ringtone.description"="RingsignalöverföringssprĂ„k (RTTTL) RingsignalstrĂ€ng som anvĂ€nds av stödda buzzers i externa notifikationer."; +"config.module.paxcounter.settings"="PAX RĂ€knare"; +"config.module.paxcounter.title"="PAX RĂ€knare Konfiguration"; +"config.module.paxcounter.enabled.description"="NĂ€r aktiverad rĂ€knar PAX-rĂ€knarmodulen antalet personer som passerar med WiFi och Bluetooth. BĂ„de WiFi och Bluetooth mĂ„ste vara aktiverade för att PAX-rĂ€knaren ska fungera."; +"config.module.paxcounter.updateinterval"="Uppdateringsintervall"; +"config.module.paxcounter.updateinterval.description"="Hur ofta vi kan skicka ett meddelande till mesh-nĂ€tverket nĂ€r personer upptĂ€cks."; +"config.save.confirm"="Efter att konfigurationsvĂ€rdena sparats kommer noden att starta om."; +"communicating"="Kommunicerar med enheten..."; +"connected.radio"="Ansluten Radio"; +"connected"="Bluetooth Ansluten"; +"connecting"="Ansluter..."; +"contacts"="Kontakter"; +"contacts %@"="Kontakter (%@)"; +"copy"="Kopiera"; +"current"="Aktuell"; +"default"="Standard"; +"delete"="Ta bort"; +"detection.sensor"="Detektionssensor"; +"detection.sensor.config"="Konfiguration av Detektionssensor"; +"detection.sensor.log"="Logg för Detektionssensor"; +"device"="Enhet"; +"device.config"="Enhetskonfiguration"; +"device.configuration"="EnhetsinstĂ€llningar"; +"device.metrics.delete"="Ta bort alla enhetsmĂ€tvĂ€rden?"; +"device.metrics.log"="Logg för EnhetsmĂ€tvĂ€rden"; +"device.role.client"="Appansluten eller fristĂ„ende meddelandeenhet."; +"device.role.clientmute"="Enhet som inte vidarebefordrar paket frĂ„n andra enheter."; +"device.role.clienthidden"="Enhet som endast sĂ€nder ut nĂ€r det behövs för stealth eller energibesparing."; +"device.role.tracker"="SĂ€nder ut GPS-positionspaket som prioritet."; +"device.role.lostandfound"="SĂ€nder regelbundet ut plats som meddelande till standardkanalen för att underlĂ€tta Ă„terhĂ€mtning av enheten."; +"device.role.sensor"="SĂ€nder ut telemetripaket som prioritet."; +"device.role.tak"="Optimerad för kommunikation med ATAK-systemet, minskar rutinutsĂ€ndningar."; +"device.role.taktracker"="Aktiverar automatiska TAK PLI-utsĂ€ndningar och minskar rutinutsĂ€ndningar."; +"device.role.repeater"="Infrastrukturnod för att utöka nĂ€tverkstĂ€ckningen genom att vidarebefordra meddelanden med minimal overhead. Syns inte i Noder-listan."; +"device.role.router"="Infrastrukturnod för att utöka nĂ€tverkstĂ€ckningen genom att vidarebefordra meddelanden. Synlig i Noder-listan."; +"device.role.routerclient"="Kombination av bĂ„de ROUTER och CLIENT. Inte för mobila enheter."; +"direct.messages"="Direktmeddelanden"; +"dismiss.keyboard"="StĂ€ng"; +"display"="SkĂ€rm"; +"display.config"="SkĂ€rmkonfiguration"; +"distance"="Distans"; +"disconnect"="Koppla frĂ„n"; +"echo"="Eko"; +"email.address"="E-postadress"; +"enabled"="Aktiverad"; +"encrypted"="Krypterad"; +"external.notification"="Extern Notifikation"; +"external.notification.config"="Konfiguration av Extern Notifikation"; +"finish"="Avsluta"; +"firmware.version"="Firmwareversion"; +"firmware.version.unsupported"="OkĂ€nd Firmwareversion upptĂ€ckt, kan inte ansluta till enheten."; +"gas"="Gas"; +"gas.resistance"="GasmotstĂ„nd"; +"generate.qr.code"="Generera QR-kod"; +"gpsformat.dec"="Decimalgrader"; +"gpsformat.dms"="Grader Minuter Sekunder"; +"gpsformat.utm"="Universal Transversal Mercator"; +"gpsformat.mgrs"="MilitĂ€rt rutnĂ€tsreferenssystem"; +"gpsformat.olc"="Öppen Platskod (Ă€ven kĂ€nd som Pluskoder)"; +"gpsformat.osgr"="Ordnance Survey RutnĂ€tsreferens"; +"gpsmode.disabled"="Inaktiverad"; +"gpsmode.enabled"="Aktiverad"; +"gpsmode.notPresent"="Inte nĂ€rvarande"; +"heard"="Hörd"; +"heard.last"="Senast Hörd"; +"hybrid"="Hybrid"; +"hybrid.flyover"="Hybrid Flygöversikt"; +"include"="Inkludera"; +"inputevent.none"="Ingen"; +"inputevent.up"="Upp"; +"inputevent.down"="Ner"; +"inputevent.left"="VĂ€nster"; +"inputevent.right"="Höger"; +"inputevent.select"="VĂ€lj"; +"inputevent.back"="BakĂ„t"; +"inputevent.cancel"="Avbryt"; +"interval.one.second"="En Sekund"; +"interval.two.seconds"="TvĂ„ Sekunder"; +"interval.three.seconds"="Tre Sekunder"; +"interval.four.seconds"="Fyra Sekunder"; +"interval.five.seconds"="Fem Sekunder"; +"interval.ten.seconds"="Tio Sekunder"; +"interval.fifteen.seconds"="Femton Sekunder"; +"interval.twenty.seconds"="Tjugo Sekunder"; +"interval.twentyfive.seconds"="Tjugofem Sekunder"; +"interval.thirty.seconds"="Trettio Sekunder"; +"interval.fortyfive.seconds"="Fyrtiofem Sekunder"; +"interval.one.minute"="En Minut"; +"interval.two.minutes"="TvĂ„ Minuter"; +"interval.five.minutes"="Fem Minuter"; +"interval.ten.minutes"="Tio Minuter"; +"interval.fifteen.minutes"="Femton Minuter"; +"interval.thirty.minutes"="Trettio Minuter"; +"interval.one.hour"="En Timme"; +"interval.two.hours"="TvĂ„ Timmar"; +"interval.three.hours"="Tre Timmar"; +"interval.four.hours"="Fyra Timmar"; +"interval.five.hours"="Fem Timmar"; +"interval.six.hours"="Sex Timmar"; +"interval.twelve.hours"="Tolv Timmar"; +"interval.eighteen.hours"="Arton Timmar"; +"interval.twentyfour.hours"="Tjugofyra Timmar"; +"interval.thirtysix.hours"="Trettiosex Timmar"; +"interval.fortyeight.hours"="FyrtioĂ„tta Timmar"; +"interval.seventytwo.hours"="SjuttiotvĂ„ Timmar"; +"keyboard.type"="Tangentbordstyp"; +"logging"="Loggning"; +"lora"="LoRa"; +"lora.config"="LoRa Konfiguration"; +"map"="Mesh Karta"; +"map.type"="Standardtyp"; +"map.centering"="CentreringslĂ€ge"; +"map.tiles.delete"="Radera Alla Kartplattor"; +"map.recentering"="Automatisk Centrering"; +"map.use.legacy"="AnvĂ€nd Äldre Mesh Karta"; +"map.usertrackingmode"="SpĂ„rningslĂ€ge för anvĂ€ndare"; +"map.usertrackingmode.follow"="Följ"; +"map.usertrackingmode.followwithheading"="Följ med riktning"; +"map.usertrackingmode.none"="Ingen"; +"mesh.live.activity"="Mesh Live Aktivitet"; +"mesh.log"="Mesh-logg"; +"mesh.log.ambientlighting.config %@"="Konfiguration för omgivningsbelysningsmodulen mottagen: %@"; +"mesh.log.bluetooth.config %@"="Bluetooth-konfiguration mottagen: %@"; +"mesh.log.cannedmessage.config %@"="Konfiguration för modulen med fördefinierade meddelanden mottagen: %@"; +"mesh.log.cannedmessages.messages.get %@"="BegĂ€rda meddelanden för modulen med fördefinierade meddelanden för nod: %@"; +"mesh.log.cannedmessages.messages.received %@"="Mottagna meddelanden för fördefinierade meddelanden För: %@"; +"mesh.log.channel.sent %@ %d"="Skickade en kanal för: %@ Kanalindex %d"; +"mesh.log.channel.received %d %@"="Kanal %d mottagen frĂ„n: %@"; +"mesh.log.device.config %@"="Enhetskonfiguration mottagen: %@"; +"mesh.log.display.config %@"="SkĂ€rmkonfiguration mottagen: %@"; +"mesh.log.devicemetadata %@"="BegĂ€r metadata för enhet för %@"; +"mesh.log.device.metadata.received %@"="Metadata för enhet mottagen frĂ„n: %@"; +"mesh.log.detectionsensor.config %@"="Konfiguration för detektionssensormodulen mottagen: %@"; +"mesh.log.externalnotification.config %@"="Konfiguration för modulen för externa notifikationer mottagen: %@"; +"mesh.log.lora.config %@"="LoRa-konfiguration mottagen: %@"; +"mesh.log.lora.config.sent %@"="Skickade en LoRa.Konfiguration för: %@"; +"mesh.log.mqtt.config %@"="MQTT-modulkonfiguration mottagen: %@"; +"mesh.log.myinfo %@"="Min info mottagen: %@"; +"mesh.log.network.config %@"="NĂ€tverkskonfiguration mottagen: %@"; +"mesh.log.nodeinfo.received %@"="Nodinformation mottagen för: %@"; +"mesh.log.paxcounter %@"="PAX-rĂ€knarmeddelande mottaget frĂ„n: %@"; +"mesh.log.paxcounter.config %@"="PAX-rĂ€knarkonfiguration mottagen: %@"; +"mesh.log.position.config %@"="Positionskonfiguration mottagen: %@"; +"mesh.log.position.received %@"="Positionspaket mottaget frĂ„n nod: %@"; +"mesh.log.power.config %@"="Strömkonfiguration mottagen: %@"; +"mesh.log.rangetest.config %@"="Konfiguration för rĂ€ckviddstestmodulen mottagen: %@"; +"mesh.log.ringtone.config %@"="Konfiguration för RTTTL-ringsignal mottagen: %@"; +"mesh.log.routing.message %@ %@"="Routing mottagen för RequestID: %@ Ack Status: %@"; +"mesh.log.serial.config %@"="Seriekonfigurationsmodul mottagen: %@"; +"mesh.log.sharelocation %@"="Skickade ett positionspaket frĂ„n Apple-enhetens GPS till nod: %@"; +"mesh.log.storeforward.config %@"="Konfiguration för Store & Forward-modulen mottagen: %@"; +"mesh.log.telemetry.config %@"="Telemetrimodulkonfiguration mottagen: %@"; +"mesh.log.telemetry.received %@"="Telemetri mottagen för: %@"; +"mesh.log.textmessage.received"="Meddelande mottaget frĂ„n textmeddelandeappen."; +"mesh.log.textmessage.send.failed %@"="Misslyckades med att skicka meddelande, inte korrekt ansluten till %@"; +"mesh.log.textmessage.sent %@ %@ %@"="Skickade meddelande %@ frĂ„n %@ till %@"; +"mesh.log.traceroute.received.direct %@"="SpĂ„rruttförfrĂ„gan skickad till nod: %@ mottogs direkt."; +"mesh.log.traceroute.received.route %@"="SpĂ„rruttförfrĂ„gan returnerade: %@"; +"mesh.log.traceroute.sent %@"="Skickade en spĂ„rruttförfrĂ„gan till nod: %@"; +"mesh.log.wantconfig %@"="UtfĂ€rdar Want Config till %@"; +"mesh.log.waypoint.sent %@"="Skickade en vĂ€gpunktspaket frĂ„n: %@"; +"mesh.log.waypoint.received %@"="VĂ€gpunktspaket mottaget frĂ„n nod: %@"; +"message"="Meddelande"; +"message.details"="Meddelandedetaljer"; +"messages"="Meddelanden"; +"mode"="LĂ€ge"; +"module.configuration"="Modulkonfiguration"; +"mqtt"="MQTT"; +"mqtt.connect"="Anslut till MQTT"; +"mqtt.config"="MQTT-konfiguration"; +"mqtt.clientproxy"="MQTT-klientproxy"; +"mqtt.disconnect"="Koppla frĂ„n MQTT"; +"mqtt.username"="AnvĂ€ndarnamn"; +"name"="Namn"; +"network"="NĂ€tverk"; +"network.config"="NĂ€tverkskonfiguration"; +"nodes"="Noder"; +"nodes %@"="Noder (%@)"; +"nodelist.filter.distance %@"="upp till %@ bort"; +"save.config %@"="Spara konfiguration för %@"; +"no.nodes"="Inga Meshtastic-noder hittades"; +"not.connected"="Ingen enhet ansluten"; +"numbers.punctuation"="Siffror och skiljetecken"; +"off"="Av"; +"offline"="Offline"; +"on.boot"="Endast vid uppstart"; +"options"="Alternativ"; +"password"="Lösenord"; +"pause"="Pausa"; +"paxcounter.ble"="BLE"; +"paxcounter.delete"="Radera all paxdata?"; +"paxcounter.wifi"="WiFi"; +"paxcounter.uptime"="Drifttid"; +"paxcounter.content.unavailable"="Inga loggar för PAX-rĂ€knare"; +"paxcounter.log"="PAX-rĂ€knarens logg"; +"paxcounter.total"="Totalt PAX"; +"phone.gps"="Telefon-GPS"; +"phone.gps.interval.description"="Hur ofta din telefon skickar din plats till enheten, platsuppdateringar till mesh-nĂ€tverket hanteras av enheten."; +"position"="Position"; +"position.config"="Positionskonfiguration"; +"position.precision %@"="Inom %@"; +"preferred.radio"="Föredragen Radio"; +"radio.configuration"="RadioinstĂ€llningar"; +"range.test"="RĂ€ckviddstest"; +"range.test.blocked"="Blockera rĂ€ckviddstest"; +"range.test.config"="Konfiguration av rĂ€ckviddstest"; +"reply"="Svara"; +"reboot"="Starta om"; +"reboot.node"="Starta om nod?"; +"received.ack"="Mottaget kvitto"; +"received.ack.real"="Mottagarkvitto"; +"resume"="Återuppta"; +"ringtone"="Ringsignal"; +"ringtone.config"="RingsignalsinstĂ€llningar"; +"route.recorder"="Ruttinspelare"; +"routes"="Rutter"; +"routing.acknowledged"="BekrĂ€ftad"; +"routing.noroute"="Ingen rutt"; +"routing.gotnak"="Mottog ett negativt kvitto"; +"routing.timeout"="TidsgrĂ€ns överskriden"; +"routing.nointerface"="Inget grĂ€nssnitt"; +"routing.maxretransmit"="Max antal omsĂ€ndningar nĂ„tt"; +"routing.nochannel"="Ingen kanal"; +"routing.toolarge"="Paketet Ă€r för stort"; +"routing.noresponse"="Inget svar"; +"routing.dutycyclelimit"="Regionala sĂ€ndningsgrĂ€nsen nĂ„dd"; +"routing.badRequest"="Felaktig begĂ€ran"; +"routing.notauthorized"="Inte auktoriserad"; +"satellite"="Satellit"; +"satellite.flyover"="Satellitöverflygning"; +"save"="Spara"; +"save.config %@"="Spara konfiguration för %@"; +"serial"="Serie"; +"serial.config"="Seriekonfiguration"; +"serial.mode.default"="Standard"; +"serial.mode.simple"="Enkel"; +"serial.mode.proto"="Protobufs"; +"serial.mode.txtmsg"="Textmeddelande"; +"serial.mode.nmea"="NMEA-positioner"; +"settings"="InstĂ€llningar"; +"share.channels"="Dela QR-kod"; +"share.position"="Dela position"; +"subscribed"="Prenumererar pĂ„ mesh"; +"select.contact"="VĂ€lj en kontakt"; +"select.node"="VĂ€lj en nod"; +"select.menu.item"="VĂ€lj ett alternativ frĂ„n menyn"; +"set.region"="StĂ€ll in LoRa-region"; +"standard"="Standard"; +"standard.muted"="Standard Muted"; +"start"="Start"; +"storeforward"="Lagra & VideresĂ€nd"; +"storeforward.config"="Konfiguration för Lagra & VideresĂ€nd"; +"storeforward.heartbeat"="Skicka hjĂ€rtslag"; +"ssid"="SSID"; +"tapback"="Svarsreaktion"; +"tapback.heart"="HjĂ€rta"; +"tapback.thumbsup"="Tummen upp"; +"tapback.thumbsdown"="Tummen ner"; +"tapback.haha"="HaHa"; +"tapback.exclamation"="Utropstecken"; +"tapback.question"="FrĂ„getecken"; +"tapback.poop"="Bajs"; +"tapback.wave"="Vinka"; +"telemetry"="Telemetri (Sensorer)"; +"telemetry.config"="TelemetriinstĂ€llningar"; +"timeout"="TidsgrĂ€ns överskriden"; +"timestamp"="TidsstĂ€mpel"; +"tip.bluetooth.connect.title"="Ansluten Radio"; +"tip.bluetooth.connect.message"="Visar information för LoRa-radion ansluten via bluetooth. Du kan svepa Ă„t vĂ€nster för att koppla frĂ„n radion och lĂ„ngtryck för att visa statistik eller starta liveaktivitet."; +"tip.channel.admin.title"="Administratörskanal"; +"tip.channel.admin.message"="Administratörskanal upptĂ€ckt: VĂ€lj en nod frĂ„n rullgardinsmenyn för att hantera anslutna eller fjĂ€rranslutna enheter."; +"tip.channels.create.title"="Hantera Kanaler"; +"tip.channels.create.message"="De flesta data i ditt mesh-nĂ€tverk skickas över primĂ€rkanalen. Du kan stĂ€lla in sekundĂ€ra kanaler för att skapa ytterligare meddelandegrupper skyddade av sin egen nyckel. Tips för kanalkonfiguration"; +"tip.channels.share.title"="Dela Meshtastic-kanaler"; +"tip.channels.share.message"="En Meshtastic QR-kod innehĂ„ller LoRa-konfigurationen och kanalvĂ€rden som behövs för kommunikation. De flesta aktiviteter i mesh-nĂ€tverket sker pĂ„ den obligatoriska primĂ€rkanalen. Om du inte delar din primĂ€rkanal blir din första delade kanal primĂ€rkanalen pĂ„ det andra nĂ€tverket. Andra kanaler Ă€r för privata grupper, varje med sin egen nyckel."; +"tip.messages.title"="Meddelanden"; +"tip.messages.message"="Du kan skicka och ta emot kanalmeddelanden (gruppchatt) och direkta meddelanden. FrĂ„n alla meddelanden kan du lĂ„ngtrycka för att se tillgĂ€ngliga Ă„tgĂ€rder som kopiera, svara, tapback och radera samt leveransdetaljer."; +"twitter"="Twitter"; +"unknown"="OkĂ€nd"; +"unknown.age"="OkĂ€nd Ă„lder"; +"unset"="ÅterstĂ€ll"; +"update.firmware"="Uppdatera din firmware"; +"update.interval"="Uppdateringsintervall"; +"user"="AnvĂ€ndare"; +"user.details"="AnvĂ€ndaruppgifter"; +"voltage"="SpĂ€nning"; +"waiting"="VĂ€ntar..."; From a80502484e676a0512768d96808878104905098c Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 8 Apr 2024 08:21:37 -0700 Subject: [PATCH 10/13] Stop reseting app settings when connecting a new device as fixed position as been refactored. Make trace route positions be 24 hours --- Meshtastic/Helpers/BLEManager.swift | 2 +- Meshtastic/Views/Bluetooth/Connect.swift | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index a47384d3..f7191f0b 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -418,7 +418,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate // Grab the most recent postion, within the last hour if connectedNode?.positions?.count ?? 0 > 0 { let mostRecent = connectedNode?.positions?.lastObject as! PositionEntity - if mostRecent.time! >= Calendar.current.date(byAdding: .minute, value: -60, to: Date())! { + if mostRecent.time! >= Calendar.current.date(byAdding: .hour, value: -24, to: Date())! { traceRoute.altitude = mostRecent.altitude traceRoute.latitudeI = mostRecent.latitudeI traceRoute.longitudeI = mostRecent.longitudeI diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index 42d23865..5a53d0d9 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -209,7 +209,7 @@ struct Connect: View { }.padding([.bottom, .top]) } } - .confirmationDialog("Connecting to a new radio will clear all local app data on the phone and will reset all app specific settings.", isPresented: $presentingSwitchPreferredPeripheral, titleVisibility: .visible) { + .confirmationDialog("Connecting to a new radio will clear all local app data on the phone.", isPresented: $presentingSwitchPreferredPeripheral, titleVisibility: .visible) { Button("Connect to new radio?", role: .destructive) { UserDefaults.preferredPeripheralId = selectedPeripherialId @@ -218,7 +218,6 @@ struct Connect: View { bleManager.disconnectPeripheral() } clearCoreDataDatabase(context: context) - UserDefaults.standard.reset() let radio = bleManager.peripherals.first(where: { $0.peripheral.identifier.uuidString == selectedPeripherialId }) if radio != nil { From 178e0deda58fd4e399af42d4beccfd7fa8fc4912 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 8 Apr 2024 10:51:04 -0700 Subject: [PATCH 11/13] Swedish translation --- Meshtastic.xcodeproj/project.pbxproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 4017318d..dadd5c76 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -479,6 +479,7 @@ DDE9659B2B1C3B6A00531070 /* RouteRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteRecorder.swift; sourceTree = ""; }; DDEE03EC29544A1000FCAD57 /* MeshtasticDataModelV4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV4.xcdatamodel; sourceTree = ""; }; DDF45C332BC1A48E005ED5F2 /* MQTTIcon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MQTTIcon.swift; sourceTree = ""; }; + DDF45C352BC465B2005ED5F2 /* se */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = se; path = se.lproj/Localizable.strings; sourceTree = ""; }; DDF6B2462A9AEB9E00BA6931 /* MeshtasticDataModelV17.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV17.xcdatamodel; sourceTree = ""; }; DDF6B2472A9AEBF500BA6931 /* StoreForwardConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreForwardConfig.swift; sourceTree = ""; }; DDF6B24B2A9C2FC800BA6931 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; @@ -1141,6 +1142,7 @@ he, fr, "zh-Hant-TW", + se, ); mainGroup = DDC2E14B26CE248E0042C5E4; packageReferences = ( @@ -1465,6 +1467,7 @@ DD31EC492B7F18B7006A3995 /* he */, DDDC22312BA76701002C44F1 /* fr */, DDDC22322BA76961002C44F1 /* zh-Hant-TW */, + DDF45C352BC465B2005ED5F2 /* se */, ); name = Localizable.strings; sourceTree = ""; From 9beccc163988e5aa37cb69cad855866ab73b1e28 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 8 Apr 2024 10:59:38 -0700 Subject: [PATCH 12/13] fix delete waypoint bugs --- Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift b/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift index 446ed2c0..0869ff2b 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift @@ -195,8 +195,8 @@ struct WaypointForm: View { newWaypoint.id = UInt32(waypoint.id) newWaypoint.name = name.count > 0 ? name : "Dropped Pin" newWaypoint.description_p = description - newWaypoint.latitudeI = waypoint.longitudeI - newWaypoint.longitudeI = waypoint.latitudeI + newWaypoint.latitudeI = waypoint.latitudeI + newWaypoint.longitudeI = waypoint.longitudeI // Unicode scalar value for the icon emoji string let unicodeScalers = icon.unicodeScalars // First element as an UInt32 @@ -209,7 +209,7 @@ struct WaypointForm: View { newWaypoint.lockedTo = UInt32(lockedTo) } } - newWaypoint.expire = UInt32(expire.timeIntervalSince1970) + newWaypoint.expire = UInt32(1) if bleManager.sendWaypoint(waypoint: newWaypoint) { bleManager.context!.delete(waypoint) From 8156777fea3f5995344044fd7daf6eb1948c1f6b Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 8 Apr 2024 11:41:54 -0700 Subject: [PATCH 13/13] Timezone --- Meshtastic.xcodeproj/project.pbxproj | 8 +- Meshtastic/Extensions/TimeZone.swift | 72 +++ .../Meshtastic.xcdatamodeld/.xccurrentversion | 2 +- .../contents | 462 ++++++++++++++ Meshtastic/Persistence/UpdateCoreData.swift | 2 + .../Protobufs/meshtastic/config.pb.swift | 10 + .../Protobufs/meshtastic/deviceonly.pb.swift | 594 +++++++++--------- Meshtastic/Protobufs/meshtastic/mesh.pb.swift | 17 + .../Views/Settings/Config/DeviceConfig.swift | 34 +- protobufs | 2 +- 10 files changed, 902 insertions(+), 301 deletions(-) create mode 100644 Meshtastic/Extensions/TimeZone.swift create mode 100644 Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV33.xcdatamodel/contents diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index dadd5c76..ace31a47 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -202,6 +202,7 @@ DDE5B4062B227E3200FCDD05 /* TraceRouteEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE5B4052B227E3200FCDD05 /* TraceRouteEntityExtension.swift */; }; DDE9659C2B1C3B6A00531070 /* RouteRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE9659B2B1C3B6A00531070 /* RouteRecorder.swift */; }; DDF45C342BC1A48E005ED5F2 /* MQTTIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF45C332BC1A48E005ED5F2 /* MQTTIcon.swift */; }; + DDF45C372BC46A5A005ED5F2 /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF45C362BC46A5A005ED5F2 /* TimeZone.swift */; }; DDF6B2482A9AEBF500BA6931 /* StoreForwardConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF6B2472A9AEBF500BA6931 /* StoreForwardConfig.swift */; }; DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF924C926FBB953009FE055 /* ConnectedDevice.swift */; }; DDFEB3BB29900C1200EE7472 /* CurrentConditionsCompact.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */; }; @@ -480,6 +481,8 @@ DDEE03EC29544A1000FCAD57 /* MeshtasticDataModelV4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV4.xcdatamodel; sourceTree = ""; }; DDF45C332BC1A48E005ED5F2 /* MQTTIcon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MQTTIcon.swift; sourceTree = ""; }; DDF45C352BC465B2005ED5F2 /* se */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = se; path = se.lproj/Localizable.strings; sourceTree = ""; }; + DDF45C362BC46A5A005ED5F2 /* TimeZone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeZone.swift; sourceTree = ""; }; + DDF45C382BC46B16005ED5F2 /* MeshtasticDataModelV33.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV33.xcdatamodel; sourceTree = ""; }; DDF6B2462A9AEB9E00BA6931 /* MeshtasticDataModelV17.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV17.xcdatamodel; sourceTree = ""; }; DDF6B2472A9AEBF500BA6931 /* StoreForwardConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreForwardConfig.swift; sourceTree = ""; }; DDF6B24B2A9C2FC800BA6931 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; @@ -994,6 +997,7 @@ DDB75A102A059258006ED576 /* Url.swift */, DD1933772B084F4200771CD5 /* Measurement.swift */, DDFFA7462B3A7F3C004730DB /* Bundle.swift */, + DDF45C362BC46A5A005ED5F2 /* TimeZone.swift */, ); path = Extensions; sourceTree = ""; @@ -1297,6 +1301,7 @@ DD3619152B1EF9F900C41C8C /* LocationsHandler.swift in Sources */, DDDB444A29F8AA3A00EE2349 /* CLLocationCoordinate2D.swift in Sources */, DD41582628582E9B009B0E59 /* DeviceConfig.swift in Sources */, + DDF45C372BC46A5A005ED5F2 /* TimeZone.swift in Sources */, DD007BAE2AA4E91200F5FA12 /* MyInfoEntityExtension.swift in Sources */, DD33DB622B3D27C7003E1EA0 /* FirmwareApi.swift in Sources */, DD3CC6B528E33FD100FA9159 /* ShareChannels.swift in Sources */, @@ -1918,6 +1923,7 @@ DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + DDF45C382BC46B16005ED5F2 /* MeshtasticDataModelV33.xcdatamodel */, DD9681A22BBB22BE00FD2C47 /* MeshtasticDataModelV32.xcdatamodel */, DDDCD5712BB3246500BE6B60 /* MeshtasticDataModelV 31.xcdatamodel */, DD9A1A912BA2D2D3001E602E /* MeshtasticDataModelV 30.xcdatamodel */, @@ -1951,7 +1957,7 @@ DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */, DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */, ); - currentVersion = DD9681A22BBB22BE00FD2C47 /* MeshtasticDataModelV32.xcdatamodel */; + currentVersion = DDF45C382BC46B16005ED5F2 /* MeshtasticDataModelV33.xcdatamodel */; name = Meshtastic.xcdatamodeld; path = Meshtastic/Meshtastic.xcdatamodeld; sourceTree = ""; diff --git a/Meshtastic/Extensions/TimeZone.swift b/Meshtastic/Extensions/TimeZone.swift new file mode 100644 index 00000000..593c3d09 --- /dev/null +++ b/Meshtastic/Extensions/TimeZone.swift @@ -0,0 +1,72 @@ +// +// TimeZone.swift +// Meshtastic +// +// Copyright(C) Garth Vander Houwen 4/8/24. +// +import Foundation + +extension TimeZone { + var posixDescription: String { + if let nextDate = nextDaylightSavingTimeTransition, let afterDate = nextDaylightSavingTimeTransition(after: nextDate) { + // This timezone observes DST + + // Get the transition dates to/from standard/DST + let stdDate: Date + let dstDate: Date + if isDaylightSavingTime(for: nextDate) { + stdDate = afterDate + dstDate = nextDate + } else { + stdDate = nextDate + dstDate = afterDate + } + + // Append the standard abbreviation + var res = posixAbbreviation(for: stdDate) + // Append the standard offset + res += posixOffset(for: stdDate) + // Append the DST abbreviation + res += posixAbbreviation(for: dstDate) + + // Append the DST offset if it's not 1 hour different + let diff = secondsFromGMT(for: stdDate) - secondsFromGMT(for: dstDate) + if abs(diff) != 3600 { + res += posixOffset(for: dstDate) + } + + // Get month, weekday ordinal, weekday, hour, minutes, and second + // weekday gets returned as 1-based but we need 0-based + // The hour is based on the post-transition time but we need the pre-transition time + var cal = Calendar(identifier: .gregorian) + cal.timeZone = self + let stdcomps = cal.dateComponents([.month, .weekdayOrdinal, .weekday, .hour, .minute, .second], from: stdDate) + let dstcomps = cal.dateComponents([.month, .weekdayOrdinal, .weekday, .hour, .minute, .second], from: dstDate) + + res += String(format: ",M%d.%d.%d/%d:%02d:%02d", dstcomps.month!, dstcomps.weekdayOrdinal!, dstcomps.weekday! - 1, dstcomps.hour! - 1, dstcomps.minute!, dstcomps.second!) + res += String(format: ",M%d.%d.%d/%d:%02d:%02d", stdcomps.month!, stdcomps.weekdayOrdinal!, stdcomps.weekday! - 1, stdcomps.hour! + 1, stdcomps.minute!, stdcomps.second!) + + return res + } else { + // This timezone does not observe DST + return "\(posixAbbreviation())\(posixOffset())" + } + } + + private func posixAbbreviation(for date: Date = Date()) -> String { + let abrev = abbreviation(for: date) ?? "" // We never actually get "" for any TimeZone identifier + // Many abbreviations come in the form "GMT+X" or "GMT-X" + return abrev.hasPrefix("GMT") ? "GMT" : abrev + } + + private func posixOffset(for date: Date = Date()) -> String { + // The POSIX offset is the opposite of the GMT offset + let secs = 0 - secondsFromGMT(for: date) + let h = secs / 3600 + let m = abs(secs) % 3600 / 60 + let s = abs(secs) % 60 + + // Show the hour, only show the minutes and seconds if non-zero + return "\(h)\(m == 0 && s == 0 ? "" : ":\(String(format: "%02d", m))")\(s == 0 ? "" : ":\(String(format: "%02d", s))")" + } +} diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index 75e1f5e3..7da03638 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModelV32.xcdatamodel + MeshtasticDataModelV33.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV33.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV33.xcdatamodel/contents new file mode 100644 index 00000000..892de6ae --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV33.xcdatamodel/contents @@ -0,0 +1,462 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 8841e7c2..1ac1b6e2 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -437,6 +437,7 @@ func upsertDeviceConfigPacket(config: Meshtastic.Config.DeviceConfig, nodeNum: I newDeviceConfig.nodeInfoBroadcastSecs = Int32(truncating: config.nodeInfoBroadcastSecs as NSNumber) newDeviceConfig.doubleTapAsButtonPress = config.doubleTapAsButtonPress newDeviceConfig.isManaged = config.isManaged + newDeviceConfig.tzdef = config.tzdef fetchedNode[0].deviceConfig = newDeviceConfig } else { fetchedNode[0].deviceConfig?.role = Int32(config.role.rawValue) @@ -448,6 +449,7 @@ func upsertDeviceConfigPacket(config: Meshtastic.Config.DeviceConfig, nodeNum: I fetchedNode[0].deviceConfig?.nodeInfoBroadcastSecs = Int32(truncating: config.nodeInfoBroadcastSecs as NSNumber) fetchedNode[0].deviceConfig?.doubleTapAsButtonPress = config.doubleTapAsButtonPress fetchedNode[0].deviceConfig?.isManaged = config.isManaged + fetchedNode[0].deviceConfig?.tzdef = config.tzdef } do { try context.save() diff --git a/Meshtastic/Protobufs/meshtastic/config.pb.swift b/Meshtastic/Protobufs/meshtastic/config.pb.swift index cb99fded..c0e81a83 100644 --- a/Meshtastic/Protobufs/meshtastic/config.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/config.pb.swift @@ -190,6 +190,10 @@ struct Config { /// Disables the triple-press of user button to enable or disable GPS var disableTripleClick: Bool = false + /// + /// POSIX Timezone definition string from https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv. + var tzdef: String = String() + var unknownFields = SwiftProtobuf.UnknownStorage() /// @@ -1734,6 +1738,7 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl 8: .standard(proto: "double_tap_as_button_press"), 9: .standard(proto: "is_managed"), 10: .standard(proto: "disable_triple_click"), + 11: .same(proto: "tzdef"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -1752,6 +1757,7 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl case 8: try { try decoder.decodeSingularBoolField(value: &self.doubleTapAsButtonPress) }() case 9: try { try decoder.decodeSingularBoolField(value: &self.isManaged) }() case 10: try { try decoder.decodeSingularBoolField(value: &self.disableTripleClick) }() + case 11: try { try decoder.decodeSingularStringField(value: &self.tzdef) }() default: break } } @@ -1788,6 +1794,9 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl if self.disableTripleClick != false { try visitor.visitSingularBoolField(value: self.disableTripleClick, fieldNumber: 10) } + if !self.tzdef.isEmpty { + try visitor.visitSingularStringField(value: self.tzdef, fieldNumber: 11) + } try unknownFields.traverse(visitor: &visitor) } @@ -1802,6 +1811,7 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl if lhs.doubleTapAsButtonPress != rhs.doubleTapAsButtonPress {return false} if lhs.isManaged != rhs.isManaged {return false} if lhs.disableTripleClick != rhs.disableTripleClick {return false} + if lhs.tzdef != rhs.tzdef {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/Meshtastic/Protobufs/meshtastic/deviceonly.pb.swift b/Meshtastic/Protobufs/meshtastic/deviceonly.pb.swift index 3080ae9f..433bffd3 100644 --- a/Meshtastic/Protobufs/meshtastic/deviceonly.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/deviceonly.pb.swift @@ -21,7 +21,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP } /// -/// TODO: REPLACE +/// Font sizes for the device screen enum ScreenFonts: SwiftProtobuf.Enum { typealias RawValue = Int @@ -75,6 +75,140 @@ extension ScreenFonts: CaseIterable { #endif // swift(>=4.2) +/// +/// Position with static location information only for NodeDBLite +struct PositionLite { + // 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. + + /// + /// The new preferred location encoding, multiply by 1e-7 to get degrees + /// in floating point + var latitudeI: Int32 = 0 + + /// + /// TODO: REPLACE + var longitudeI: Int32 = 0 + + /// + /// In meters above MSL (but see issue #359) + var altitude: Int32 = 0 + + /// + /// This is usually not sent over the mesh (to save space), but it is sent + /// from the phone so that the local device can set its RTC If it is sent over + /// the mesh (because there are devices on the mesh without GPS), it will only + /// be sent by devices which has a hardware GPS clock. + /// seconds since 1970 + var time: UInt32 = 0 + + /// + /// TODO: REPLACE + var locationSource: Position.LocSource = .locUnset + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct NodeInfoLite { + // 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. + + /// + /// The node number + var num: UInt32 { + get {return _storage._num} + set {_uniqueStorage()._num = newValue} + } + + /// + /// The user info for this node + var user: User { + get {return _storage._user ?? User()} + set {_uniqueStorage()._user = newValue} + } + /// Returns true if `user` has been explicitly set. + var hasUser: Bool {return _storage._user != nil} + /// Clears the value of `user`. Subsequent reads from it will return its default value. + mutating func clearUser() {_uniqueStorage()._user = nil} + + /// + /// This position data. Note: before 1.2.14 we would also store the last time we've heard from this node in position.time, that is no longer true. + /// Position.time now indicates the last time we received a POSITION from that node. + var position: PositionLite { + get {return _storage._position ?? PositionLite()} + set {_uniqueStorage()._position = newValue} + } + /// Returns true if `position` has been explicitly set. + var hasPosition: Bool {return _storage._position != nil} + /// Clears the value of `position`. Subsequent reads from it will return its default value. + mutating func clearPosition() {_uniqueStorage()._position = nil} + + /// + /// Returns the Signal-to-noise ratio (SNR) of the last received message, + /// as measured by the receiver. Return SNR of the last received message in dB + var snr: Float { + get {return _storage._snr} + set {_uniqueStorage()._snr = newValue} + } + + /// + /// Set to indicate the last time we received a packet from this node + var lastHeard: UInt32 { + get {return _storage._lastHeard} + set {_uniqueStorage()._lastHeard = newValue} + } + + /// + /// The latest device metrics for the node. + var deviceMetrics: DeviceMetrics { + get {return _storage._deviceMetrics ?? DeviceMetrics()} + set {_uniqueStorage()._deviceMetrics = newValue} + } + /// Returns true if `deviceMetrics` has been explicitly set. + var hasDeviceMetrics: Bool {return _storage._deviceMetrics != nil} + /// Clears the value of `deviceMetrics`. Subsequent reads from it will return its default value. + mutating func clearDeviceMetrics() {_uniqueStorage()._deviceMetrics = nil} + + /// + /// local channel index we heard that node on. Only populated if its not the default channel. + var channel: UInt32 { + get {return _storage._channel} + set {_uniqueStorage()._channel = newValue} + } + + /// + /// True if we witnessed the node over MQTT instead of LoRA transport + var viaMqtt: Bool { + get {return _storage._viaMqtt} + set {_uniqueStorage()._viaMqtt = newValue} + } + + /// + /// Number of hops away from us this node is (0 if adjacent) + var hopsAway: UInt32 { + get {return _storage._hopsAway} + set {_uniqueStorage()._hopsAway = newValue} + } + + /// + /// True if node is in our favorites list + /// Persists between NodeDB internal clean ups + var isFavorite: Bool { + get {return _storage._isFavorite} + set {_uniqueStorage()._isFavorite = newValue} + } + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _storage = _StorageClass.defaultInstance +} + /// /// This message is never sent over the wire, but it is used for serializing DB /// state to flash in the device code @@ -187,140 +321,6 @@ struct DeviceState { fileprivate var _storage = _StorageClass.defaultInstance } -struct NodeInfoLite { - // 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. - - /// - /// The node number - var num: UInt32 { - get {return _storage._num} - set {_uniqueStorage()._num = newValue} - } - - /// - /// The user info for this node - var user: User { - get {return _storage._user ?? User()} - set {_uniqueStorage()._user = newValue} - } - /// Returns true if `user` has been explicitly set. - var hasUser: Bool {return _storage._user != nil} - /// Clears the value of `user`. Subsequent reads from it will return its default value. - mutating func clearUser() {_uniqueStorage()._user = nil} - - /// - /// This position data. Note: before 1.2.14 we would also store the last time we've heard from this node in position.time, that is no longer true. - /// Position.time now indicates the last time we received a POSITION from that node. - var position: PositionLite { - get {return _storage._position ?? PositionLite()} - set {_uniqueStorage()._position = newValue} - } - /// Returns true if `position` has been explicitly set. - var hasPosition: Bool {return _storage._position != nil} - /// Clears the value of `position`. Subsequent reads from it will return its default value. - mutating func clearPosition() {_uniqueStorage()._position = nil} - - /// - /// Returns the Signal-to-noise ratio (SNR) of the last received message, - /// as measured by the receiver. Return SNR of the last received message in dB - var snr: Float { - get {return _storage._snr} - set {_uniqueStorage()._snr = newValue} - } - - /// - /// Set to indicate the last time we received a packet from this node - var lastHeard: UInt32 { - get {return _storage._lastHeard} - set {_uniqueStorage()._lastHeard = newValue} - } - - /// - /// The latest device metrics for the node. - var deviceMetrics: DeviceMetrics { - get {return _storage._deviceMetrics ?? DeviceMetrics()} - set {_uniqueStorage()._deviceMetrics = newValue} - } - /// Returns true if `deviceMetrics` has been explicitly set. - var hasDeviceMetrics: Bool {return _storage._deviceMetrics != nil} - /// Clears the value of `deviceMetrics`. Subsequent reads from it will return its default value. - mutating func clearDeviceMetrics() {_uniqueStorage()._deviceMetrics = nil} - - /// - /// local channel index we heard that node on. Only populated if its not the default channel. - var channel: UInt32 { - get {return _storage._channel} - set {_uniqueStorage()._channel = newValue} - } - - /// - /// True if we witnessed the node over MQTT instead of LoRA transport - var viaMqtt: Bool { - get {return _storage._viaMqtt} - set {_uniqueStorage()._viaMqtt = newValue} - } - - /// - /// Number of hops away from us this node is (0 if adjacent) - var hopsAway: UInt32 { - get {return _storage._hopsAway} - set {_uniqueStorage()._hopsAway = newValue} - } - - /// - /// True if node is in our favorites list - /// Persists between NodeDB internal clean ups - var isFavorite: Bool { - get {return _storage._isFavorite} - set {_uniqueStorage()._isFavorite = newValue} - } - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _storage = _StorageClass.defaultInstance -} - -/// -/// Position with static location information only for NodeDBLite -struct PositionLite { - // 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. - - /// - /// The new preferred location encoding, multiply by 1e-7 to get degrees - /// in floating point - var latitudeI: Int32 = 0 - - /// - /// TODO: REPLACE - var longitudeI: Int32 = 0 - - /// - /// In meters above MSL (but see issue #359) - var altitude: Int32 = 0 - - /// - /// This is usually not sent over the mesh (to save space), but it is sent - /// from the phone so that the local device can set its RTC If it is sent over - /// the mesh (because there are devices on the mesh without GPS), it will only - /// be sent by devices which has a hardware GPS clock. - /// seconds since 1970 - var time: UInt32 = 0 - - /// - /// TODO: REPLACE - var locationSource: Position.LocSource = .locUnset - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - /// /// The on-disk saved channels struct ChannelFile { @@ -407,9 +407,9 @@ struct OEMStore { #if swift(>=5.5) && canImport(_Concurrency) extension ScreenFonts: @unchecked Sendable {} -extension DeviceState: @unchecked Sendable {} -extension NodeInfoLite: @unchecked Sendable {} extension PositionLite: @unchecked Sendable {} +extension NodeInfoLite: @unchecked Sendable {} +extension DeviceState: @unchecked Sendable {} extension ChannelFile: @unchecked Sendable {} extension OEMStore: @unchecked Sendable {} #endif // swift(>=5.5) && canImport(_Concurrency) @@ -426,141 +426,57 @@ extension ScreenFonts: SwiftProtobuf._ProtoNameProviding { ] } -extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".DeviceState" +extension PositionLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".PositionLite" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 2: .standard(proto: "my_node"), - 3: .same(proto: "owner"), - 5: .standard(proto: "receive_queue"), - 8: .same(proto: "version"), - 7: .standard(proto: "rx_text_message"), - 9: .standard(proto: "no_save"), - 11: .standard(proto: "did_gps_reset"), - 12: .standard(proto: "rx_waypoint"), - 13: .standard(proto: "node_remote_hardware_pins"), - 14: .standard(proto: "node_db_lite"), + 1: .standard(proto: "latitude_i"), + 2: .standard(proto: "longitude_i"), + 3: .same(proto: "altitude"), + 4: .same(proto: "time"), + 5: .standard(proto: "location_source"), ] - fileprivate class _StorageClass { - var _myNode: MyNodeInfo? = nil - var _owner: User? = nil - var _receiveQueue: [MeshPacket] = [] - var _version: UInt32 = 0 - var _rxTextMessage: MeshPacket? = nil - var _noSave: Bool = false - var _didGpsReset: Bool = false - var _rxWaypoint: MeshPacket? = nil - var _nodeRemoteHardwarePins: [NodeRemoteHardwarePin] = [] - var _nodeDbLite: [NodeInfoLite] = [] - - static let defaultInstance = _StorageClass() - - private init() {} - - init(copying source: _StorageClass) { - _myNode = source._myNode - _owner = source._owner - _receiveQueue = source._receiveQueue - _version = source._version - _rxTextMessage = source._rxTextMessage - _noSave = source._noSave - _didGpsReset = source._didGpsReset - _rxWaypoint = source._rxWaypoint - _nodeRemoteHardwarePins = source._nodeRemoteHardwarePins - _nodeDbLite = source._nodeDbLite - } - } - - 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 2: try { try decoder.decodeSingularMessageField(value: &_storage._myNode) }() - case 3: try { try decoder.decodeSingularMessageField(value: &_storage._owner) }() - 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) }() - case 9: try { try decoder.decodeSingularBoolField(value: &_storage._noSave) }() - case 11: try { try decoder.decodeSingularBoolField(value: &_storage._didGpsReset) }() - case 12: try { try decoder.decodeSingularMessageField(value: &_storage._rxWaypoint) }() - case 13: try { try decoder.decodeRepeatedMessageField(value: &_storage._nodeRemoteHardwarePins) }() - case 14: try { try decoder.decodeRepeatedMessageField(value: &_storage._nodeDbLite) }() - 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.decodeSingularSFixed32Field(value: &self.latitudeI) }() + case 2: try { try decoder.decodeSingularSFixed32Field(value: &self.longitudeI) }() + case 3: try { try decoder.decodeSingularInt32Field(value: &self.altitude) }() + case 4: try { try decoder.decodeSingularFixed32Field(value: &self.time) }() + case 5: try { try decoder.decodeSingularEnumField(value: &self.locationSource) }() + 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 - try { if let v = _storage._myNode { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - try { if let v = _storage._owner { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - if !_storage._receiveQueue.isEmpty { - try visitor.visitRepeatedMessageField(value: _storage._receiveQueue, fieldNumber: 5) - } - try { if let v = _storage._rxTextMessage { - try visitor.visitSingularMessageField(value: v, fieldNumber: 7) - } }() - if _storage._version != 0 { - try visitor.visitSingularUInt32Field(value: _storage._version, fieldNumber: 8) - } - if _storage._noSave != false { - try visitor.visitSingularBoolField(value: _storage._noSave, fieldNumber: 9) - } - if _storage._didGpsReset != false { - try visitor.visitSingularBoolField(value: _storage._didGpsReset, fieldNumber: 11) - } - try { if let v = _storage._rxWaypoint { - try visitor.visitSingularMessageField(value: v, fieldNumber: 12) - } }() - if !_storage._nodeRemoteHardwarePins.isEmpty { - try visitor.visitRepeatedMessageField(value: _storage._nodeRemoteHardwarePins, fieldNumber: 13) - } - if !_storage._nodeDbLite.isEmpty { - try visitor.visitRepeatedMessageField(value: _storage._nodeDbLite, fieldNumber: 14) - } + if self.latitudeI != 0 { + try visitor.visitSingularSFixed32Field(value: self.latitudeI, fieldNumber: 1) + } + if self.longitudeI != 0 { + try visitor.visitSingularSFixed32Field(value: self.longitudeI, fieldNumber: 2) + } + if self.altitude != 0 { + try visitor.visitSingularInt32Field(value: self.altitude, fieldNumber: 3) + } + if self.time != 0 { + try visitor.visitSingularFixed32Field(value: self.time, fieldNumber: 4) + } + if self.locationSource != .locUnset { + try visitor.visitSingularEnumField(value: self.locationSource, fieldNumber: 5) } try unknownFields.traverse(visitor: &visitor) } - static func ==(lhs: DeviceState, rhs: DeviceState) -> 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._myNode != rhs_storage._myNode {return false} - if _storage._owner != rhs_storage._owner {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} - if _storage._noSave != rhs_storage._noSave {return false} - if _storage._didGpsReset != rhs_storage._didGpsReset {return false} - if _storage._rxWaypoint != rhs_storage._rxWaypoint {return false} - if _storage._nodeRemoteHardwarePins != rhs_storage._nodeRemoteHardwarePins {return false} - if _storage._nodeDbLite != rhs_storage._nodeDbLite {return false} - return true - } - if !storagesAreEqual {return false} - } + static func ==(lhs: PositionLite, rhs: PositionLite) -> Bool { + if lhs.latitudeI != rhs.latitudeI {return false} + if lhs.longitudeI != rhs.longitudeI {return false} + if lhs.altitude != rhs.altitude {return false} + if lhs.time != rhs.time {return false} + if lhs.locationSource != rhs.locationSource {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -706,57 +622,141 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat } } -extension PositionLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".PositionLite" +extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".DeviceState" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "latitude_i"), - 2: .standard(proto: "longitude_i"), - 3: .same(proto: "altitude"), - 4: .same(proto: "time"), - 5: .standard(proto: "location_source"), + 2: .standard(proto: "my_node"), + 3: .same(proto: "owner"), + 5: .standard(proto: "receive_queue"), + 8: .same(proto: "version"), + 7: .standard(proto: "rx_text_message"), + 9: .standard(proto: "no_save"), + 11: .standard(proto: "did_gps_reset"), + 12: .standard(proto: "rx_waypoint"), + 13: .standard(proto: "node_remote_hardware_pins"), + 14: .standard(proto: "node_db_lite"), ] + fileprivate class _StorageClass { + var _myNode: MyNodeInfo? = nil + var _owner: User? = nil + var _receiveQueue: [MeshPacket] = [] + var _version: UInt32 = 0 + var _rxTextMessage: MeshPacket? = nil + var _noSave: Bool = false + var _didGpsReset: Bool = false + var _rxWaypoint: MeshPacket? = nil + var _nodeRemoteHardwarePins: [NodeRemoteHardwarePin] = [] + var _nodeDbLite: [NodeInfoLite] = [] + + static let defaultInstance = _StorageClass() + + private init() {} + + init(copying source: _StorageClass) { + _myNode = source._myNode + _owner = source._owner + _receiveQueue = source._receiveQueue + _version = source._version + _rxTextMessage = source._rxTextMessage + _noSave = source._noSave + _didGpsReset = source._didGpsReset + _rxWaypoint = source._rxWaypoint + _nodeRemoteHardwarePins = source._nodeRemoteHardwarePins + _nodeDbLite = source._nodeDbLite + } + } + + fileprivate mutating func _uniqueStorage() -> _StorageClass { + if !isKnownUniquelyReferenced(&_storage) { + _storage = _StorageClass(copying: _storage) + } + return _storage + } + 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.decodeSingularSFixed32Field(value: &self.latitudeI) }() - case 2: try { try decoder.decodeSingularSFixed32Field(value: &self.longitudeI) }() - case 3: try { try decoder.decodeSingularInt32Field(value: &self.altitude) }() - case 4: try { try decoder.decodeSingularFixed32Field(value: &self.time) }() - case 5: try { try decoder.decodeSingularEnumField(value: &self.locationSource) }() - default: break + _ = _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 2: try { try decoder.decodeSingularMessageField(value: &_storage._myNode) }() + case 3: try { try decoder.decodeSingularMessageField(value: &_storage._owner) }() + 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) }() + case 9: try { try decoder.decodeSingularBoolField(value: &_storage._noSave) }() + case 11: try { try decoder.decodeSingularBoolField(value: &_storage._didGpsReset) }() + case 12: try { try decoder.decodeSingularMessageField(value: &_storage._rxWaypoint) }() + case 13: try { try decoder.decodeRepeatedMessageField(value: &_storage._nodeRemoteHardwarePins) }() + case 14: try { try decoder.decodeRepeatedMessageField(value: &_storage._nodeDbLite) }() + default: break + } } } } func traverse(visitor: inout V) throws { - if self.latitudeI != 0 { - try visitor.visitSingularSFixed32Field(value: self.latitudeI, fieldNumber: 1) - } - if self.longitudeI != 0 { - try visitor.visitSingularSFixed32Field(value: self.longitudeI, fieldNumber: 2) - } - if self.altitude != 0 { - try visitor.visitSingularInt32Field(value: self.altitude, fieldNumber: 3) - } - if self.time != 0 { - try visitor.visitSingularFixed32Field(value: self.time, fieldNumber: 4) - } - if self.locationSource != .locUnset { - try visitor.visitSingularEnumField(value: self.locationSource, fieldNumber: 5) + 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 + try { if let v = _storage._myNode { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try { if let v = _storage._owner { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() + if !_storage._receiveQueue.isEmpty { + try visitor.visitRepeatedMessageField(value: _storage._receiveQueue, fieldNumber: 5) + } + try { if let v = _storage._rxTextMessage { + try visitor.visitSingularMessageField(value: v, fieldNumber: 7) + } }() + if _storage._version != 0 { + try visitor.visitSingularUInt32Field(value: _storage._version, fieldNumber: 8) + } + if _storage._noSave != false { + try visitor.visitSingularBoolField(value: _storage._noSave, fieldNumber: 9) + } + if _storage._didGpsReset != false { + try visitor.visitSingularBoolField(value: _storage._didGpsReset, fieldNumber: 11) + } + try { if let v = _storage._rxWaypoint { + try visitor.visitSingularMessageField(value: v, fieldNumber: 12) + } }() + if !_storage._nodeRemoteHardwarePins.isEmpty { + try visitor.visitRepeatedMessageField(value: _storage._nodeRemoteHardwarePins, fieldNumber: 13) + } + if !_storage._nodeDbLite.isEmpty { + try visitor.visitRepeatedMessageField(value: _storage._nodeDbLite, fieldNumber: 14) + } } try unknownFields.traverse(visitor: &visitor) } - static func ==(lhs: PositionLite, rhs: PositionLite) -> Bool { - if lhs.latitudeI != rhs.latitudeI {return false} - if lhs.longitudeI != rhs.longitudeI {return false} - if lhs.altitude != rhs.altitude {return false} - if lhs.time != rhs.time {return false} - if lhs.locationSource != rhs.locationSource {return false} + static func ==(lhs: DeviceState, rhs: DeviceState) -> 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._myNode != rhs_storage._myNode {return false} + if _storage._owner != rhs_storage._owner {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} + if _storage._noSave != rhs_storage._noSave {return false} + if _storage._didGpsReset != rhs_storage._didGpsReset {return false} + if _storage._rxWaypoint != rhs_storage._rxWaypoint {return false} + if _storage._nodeRemoteHardwarePins != rhs_storage._nodeRemoteHardwarePins {return false} + if _storage._nodeDbLite != rhs_storage._nodeDbLite {return false} + return true + } + if !storagesAreEqual {return false} + } if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/Meshtastic/Protobufs/meshtastic/mesh.pb.swift b/Meshtastic/Protobufs/meshtastic/mesh.pb.swift index a881d288..b868d7d7 100644 --- a/Meshtastic/Protobufs/meshtastic/mesh.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/mesh.pb.swift @@ -256,6 +256,15 @@ enum HardwareModel: SwiftProtobuf.Enum { /// Older "V1.0" Variant case heltecWirelessTrackerV10 // = 58 + /// + /// unPhone with ESP32-S3, TFT touchscreen, LSM6DS3TR-C accelerometer and gyroscope + case unphone // = 59 + + /// + /// Teledatics TD-LORAC NRF52840 based M.2 LoRA module + /// Compatible with the TD-WRLS development board + case tdLorac // = 60 + /// /// ------------------------------------------------------------------------------------------------------------------------------------------ /// 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. @@ -323,6 +332,8 @@ enum HardwareModel: SwiftProtobuf.Enum { case 56: self = .chatter2 case 57: self = .heltecWirelessPaperV10 case 58: self = .heltecWirelessTrackerV10 + case 59: self = .unphone + case 60: self = .tdLorac case 255: self = .privateHw default: self = .UNRECOGNIZED(rawValue) } @@ -384,6 +395,8 @@ enum HardwareModel: SwiftProtobuf.Enum { case .chatter2: return 56 case .heltecWirelessPaperV10: return 57 case .heltecWirelessTrackerV10: return 58 + case .unphone: return 59 + case .tdLorac: return 60 case .privateHw: return 255 case .UNRECOGNIZED(let i): return i } @@ -450,6 +463,8 @@ extension HardwareModel: CaseIterable { .chatter2, .heltecWirelessPaperV10, .heltecWirelessTrackerV10, + .unphone, + .tdLorac, .privateHw, ] } @@ -2745,6 +2760,8 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding { 56: .same(proto: "CHATTER_2"), 57: .same(proto: "HELTEC_WIRELESS_PAPER_V1_0"), 58: .same(proto: "HELTEC_WIRELESS_TRACKER_V1_0"), + 59: .same(proto: "UNPHONE"), + 60: .same(proto: "TD_LORAC"), 255: .same(proto: "PRIVATE_HW"), ] } diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index 64e5b330..cc4ee5ec 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -26,6 +26,7 @@ struct DeviceConfig: View { @State var nodeInfoBroadcastSecs = 10800 @State var doubleTapAsButtonPress = false @State var isManaged = false + @State var tzdef = "" var body: some View { VStack { @@ -86,6 +87,26 @@ struct DeviceConfig: View { Label("Debug Log", systemImage: "ant.fill") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + VStack(alignment: .leading) { + HStack { + Label("Time Zone", systemImage: "clock.badge.exclamationmark") + TextField("Time Zone", text: $tzdef) + .foregroundColor(.gray) + .onChange(of: tzdef, perform: { _ in + let totalBytes = tzdef.utf8.count + // Only mess with the value if it is too big + if totalBytes > 63 { + tzdef = String(tzdef.dropLast()) + } + }) + .foregroundColor(.gray) + } + .keyboardType(.default) + .disableAutocorrection(true) + Text("Time zone for dates on the device screen and log.") + .foregroundColor(.gray) + .font(.callout) + } } Section(header: Text("GPIO")) { Picker("Button GPIO", selection: $buttonGPIO) { @@ -179,6 +200,7 @@ struct DeviceConfig: View { dc.nodeInfoBroadcastSecs = UInt32(nodeInfoBroadcastSecs) dc.doubleTapAsButtonPress = doubleTapAsButtonPress dc.isManaged = isManaged + dc.tzdef = tzdef if isManaged { serialEnabled = false debugLogEnabled = false @@ -259,6 +281,11 @@ struct DeviceConfig: View { if newIsManaged != node!.deviceConfig!.isManaged { hasChanges = true } } } + .onChange(of: tzdef) { newTzdef in + if node != nil && node?.deviceConfig != nil { + if newTzdef != node!.deviceConfig!.tzdef { hasChanges = true } + } + } } func setDeviceValues() { self.deviceRole = Int(node?.deviceConfig?.role ?? 0) @@ -273,6 +300,11 @@ struct DeviceConfig: View { } self.doubleTapAsButtonPress = node?.deviceConfig?.doubleTapAsButtonPress ?? false self.isManaged = node?.deviceConfig?.isManaged ?? false - self.hasChanges = false + if self.tzdef.isEmpty { + self.tzdef = TimeZone.current.posixDescription + self.hasChanges = true + } else { + self.hasChanges = false + } } } diff --git a/protobufs b/protobufs index e6b4c590..68720ed8 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit e6b4c590e7c489306c9c44e3ad1fcf62a3efd288 +Subproject commit 68720ed8dbcb2c055e3d1ecd4f78d60692f59493