Meshtastic-Apple/Meshtastic/Views/Settings/Config/DeviceConfig.swift

330 lines
12 KiB
Swift
Raw Normal View History

2022-06-13 20:43:51 -07:00
//
// DeviceConfig.swift
// Meshtastic Apple
//
// Copyright (c) Garth Vander Houwen 6/13/22.
//
import MeshtasticProtobufs
2024-06-03 02:17:55 -07:00
import OSLog
import SwiftUI
2022-06-13 20:43:51 -07:00
struct DeviceConfig: View {
2023-08-26 23:17:30 -07:00
2022-06-13 20:43:51 -07:00
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@Environment(\.dismiss) private var goBack
2023-08-26 23:17:30 -07:00
var node: NodeInfoEntity?
2023-08-26 23:17:30 -07:00
@State private var isPresentingNodeDBResetConfirm = false
2022-07-26 21:52:36 -07:00
@State private var isPresentingFactoryResetConfirm = false
2022-06-21 02:43:37 -07:00
@State var hasChanges = false
2022-06-13 20:43:51 -07:00
@State var deviceRole = 0
2022-12-05 19:47:56 -08:00
@State var buzzerGPIO = 0
@State var buttonGPIO = 0
@State var rebroadcastMode = 0
2024-03-15 14:29:50 -07:00
@State var nodeInfoBroadcastSecs = 10800
2023-04-09 23:04:11 -07:00
@State var doubleTapAsButtonPress = false
2024-04-26 18:06:23 -07:00
@State var ledHeartbeatEnabled = true
2024-10-04 19:36:30 -07:00
@State var tripleClickAsAdHocPing = true
2024-04-08 11:41:54 -07:00
@State var tzdef = ""
2025-03-04 21:38:35 -08:00
@State private var showRouterWarning = false
2022-06-13 20:43:51 -07:00
var body: some View {
2022-06-20 00:13:04 -07:00
VStack {
Form {
ConfigHeader(title: "Device", config: \.deviceConfig, node: node, onAppear: setDeviceValues)
2025-04-26 16:05:09 -07:00
Section(header: Text("Options")) {
VStack(alignment: .leading) {
Picker("Device Role", selection: $deviceRole ) {
ForEach(DeviceRoles.allCases) { dr in
2025-03-04 21:38:35 -08:00
Text(dr.name).tag(dr.rawValue as Int)
}
}
2025-07-16 23:12:47 -07:00
.onChange(of: deviceRole) { _, newRole in
2025-07-16 23:39:07 -07:00
if hasChanges && [DeviceRoles.router.rawValue, DeviceRoles.routerLate.rawValue, DeviceRoles.repeater.rawValue].contains(newRole) {
2025-07-16 23:12:47 -07:00
showRouterWarning = true
2025-03-04 21:38:35 -08:00
}
}
.confirmationDialog(
"Are you sure?",
isPresented: $showRouterWarning,
titleVisibility: .visible
) {
Button("Confirm") {
2025-07-16 23:12:47 -07:00
hasChanges = true
2025-03-04 21:38:35 -08:00
}
Button("Cancel", role: .cancel) {
2025-07-16 23:12:47 -07:00
setDeviceValues()
}
2025-03-04 21:38:35 -08:00
} message: {
2025-07-16 23:12:47 -07:00
Text("The Router roles are only for high vantage locations like mountaintops and towers with few nearby nodes, not for use in urban areas. Improper use will hurt your local mesh.")
2022-06-13 20:43:51 -07:00
}
Text(DeviceRoles(rawValue: deviceRole)?.description ?? "")
.foregroundColor(.gray)
.font(.callout)
2022-06-13 20:43:51 -07:00
}
.pickerStyle(DefaultPickerStyle())
VStack(alignment: .leading) {
Picker("Rebroadcast Mode", selection: $rebroadcastMode ) {
ForEach(RebroadcastModes.allCases) { rm in
Text(rm.name)
}
}
Text(RebroadcastModes(rawValue: rebroadcastMode)?.description ?? "")
.foregroundColor(.gray)
.font(.callout)
}
.pickerStyle(DefaultPickerStyle())
2024-02-12 22:09:22 -08:00
Picker("Node Info Broadcast Interval", selection: $nodeInfoBroadcastSecs ) {
ForEach(UpdateIntervals.allCases) { ui in
if ui.rawValue >= 3600 {
Text(ui.description)
}
}
}
.pickerStyle(DefaultPickerStyle())
2024-04-26 18:06:23 -07:00
}
Section(header: Text("Hardware")) {
Toggle(isOn: $doubleTapAsButtonPress) {
Label("Double Tap as Button", systemImage: "hand.tap")
Text("Treat double tap on supported accelerometers as a user button press.")
2023-04-09 23:04:11 -07:00
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
2024-10-05 13:52:38 -07:00
2024-10-04 19:36:30 -07:00
Toggle(isOn: $tripleClickAsAdHocPing) {
Label("Triple Click Ad Hoc Ping", systemImage: "mappin")
2024-10-04 19:36:30 -07:00
Text("Send a position on the primary channel when the user button is triple clicked.")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
2024-04-26 18:06:23 -07:00
Toggle(isOn: $ledHeartbeatEnabled) {
Label("LED Heartbeat", systemImage: "waveform.path.ecg")
Text("Controls the blinking LED on the device. For most devices this will control one of the up to 4 LEDS, the charger and GPS LEDs are not controllable.")
2023-05-13 20:50:20 -07:00
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
2022-06-20 00:13:04 -07:00
}
Section(header: Text("Debug")) {
2024-04-08 11:41:54 -07:00
VStack(alignment: .leading) {
HStack {
Label("Time Zone", systemImage: "clock.badge.exclamationmark")
TextField("Time Zone", text: $tzdef, axis: .vertical)
2024-04-08 11:41:54 -07:00
.foregroundColor(.gray)
2024-10-05 15:50:57 -07:00
.onChange(of: tzdef) {
2024-09-22 08:03:18 -07:00
var totalBytes = tzdef.utf8.count
2024-04-08 11:41:54 -07:00
// Only mess with the value if it is too big
2024-09-22 08:03:18 -07:00
while totalBytes > 63 {
2024-04-08 11:41:54 -07:00
tzdef = String(tzdef.dropLast())
2024-09-22 08:03:18 -07:00
totalBytes = tzdef.utf8.count
2024-04-08 11:41:54 -07:00
}
2024-10-04 19:36:30 -07:00
}
2024-04-08 11:41:54 -07:00
.foregroundColor(.gray)
}
.keyboardType(.default)
.disableAutocorrection(true)
Text("Time zone for dates on the device screen and log.")
.foregroundColor(.gray)
2024-10-05 13:52:38 -07:00
.font(.callout)
2024-04-08 11:41:54 -07:00
}
}
2022-12-05 19:47:56 -08:00
Section(header: Text("GPIO")) {
Picker("Button GPIO", selection: $buttonGPIO) {
2024-01-22 13:21:17 -08:00
ForEach(0..<49) {
2022-12-05 19:47:56 -08:00
if $0 == 0 {
2025-04-27 16:19:10 -07:00
Text("Unset")
2022-12-05 19:47:56 -08:00
} else {
Text("Pin \($0)")
}
}
}
.pickerStyle(DefaultPickerStyle())
Picker("Buzzer GPIO", selection: $buzzerGPIO) {
2024-01-22 13:21:17 -08:00
ForEach(0..<49) {
2022-12-05 19:47:56 -08:00
if $0 == 0 {
2025-04-27 16:19:10 -07:00
Text("Unset")
2022-12-05 19:47:56 -08:00
} else {
Text("Pin \($0)")
}
}
}
.pickerStyle(DefaultPickerStyle())
}
2022-06-20 00:13:04 -07:00
}
.disabled(self.bleManager.connectedPeripheral == nil || node?.deviceConfig == nil)
2023-02-02 22:03:27 -08:00
// Only show these buttons for the BLE connected node
if bleManager.connectedPeripheral != nil && node?.num ?? -1 == bleManager.connectedPeripheral.num {
HStack {
Button("Reset NodeDB", role: .destructive) {
isPresentingNodeDBResetConfirm = true
}
.disabled(node?.user == nil)
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
2024-08-11 17:31:27 -07:00
.controlSize(.regular)
.padding(.leading)
2023-02-02 22:03:27 -08:00
.confirmationDialog(
2025-02-15 12:17:22 -08:00
"Are you sure?",
2023-02-02 22:03:27 -08:00
isPresented: $isPresentingNodeDBResetConfirm,
titleVisibility: .visible
) {
Button("Erase all device and app data?", role: .destructive) {
if bleManager.sendNodeDBReset(fromUser: node!.user!, toUser: node!.user!) {
2023-11-20 16:08:46 -08:00
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
bleManager.disconnectPeripheral()
clearCoreDataDatabase(context: context, includeRoutes: false)
2023-11-20 16:08:46 -08:00
}
2023-02-02 22:03:27 -08:00
} else {
2024-06-03 02:17:55 -07:00
Logger.mesh.error("NodeDB Reset Failed")
2023-02-02 22:03:27 -08:00
}
2022-10-02 09:19:03 -07:00
}
}
2023-02-02 22:03:27 -08:00
Button("Factory Reset", role: .destructive) {
isPresentingFactoryResetConfirm = true
}
.disabled(node?.user == nil)
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
2024-08-11 17:31:27 -07:00
.controlSize(.regular)
.padding(.trailing)
2023-02-02 22:03:27 -08:00
.confirmationDialog(
"Factory reset will delete device and app data.",
2023-02-02 22:03:27 -08:00
isPresented: $isPresentingFactoryResetConfirm,
titleVisibility: .visible
) {
Button("Delete all config? ", role: .destructive) {
2023-02-02 22:03:27 -08:00
if bleManager.sendFactoryReset(fromUser: node!.user!, toUser: node!.user!) {
2023-11-20 19:20:54 -08:00
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
bleManager.disconnectPeripheral()
clearCoreDataDatabase(context: context, includeRoutes: false)
2023-11-20 19:20:54 -08:00
}
2023-02-02 22:03:27 -08:00
} else {
2024-06-03 02:17:55 -07:00
Logger.mesh.error("Factory Reset Failed")
2023-02-02 22:03:27 -08:00
}
2022-10-02 09:19:03 -07:00
}
Button("Delete all config, keys and BLE bonds? ", role: .destructive) {
if bleManager.sendFactoryReset(fromUser: node!.user!, toUser: node!.user!, resetDevice: true) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
bleManager.disconnectPeripheral()
clearCoreDataDatabase(context: context, includeRoutes: false)
}
} else {
Logger.mesh.error("Factory Reset Failed")
}
}
2022-10-02 09:19:03 -07:00
}
}
}
2022-06-21 02:43:37 -07:00
HStack {
SaveConfigButton(node: node, hasChanges: $hasChanges) {
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
if connectedNode != nil {
var dc = Config.DeviceConfig()
dc.role = DeviceRoles(rawValue: deviceRole)!.protoEnumValue()
dc.buttonGpio = UInt32(buttonGPIO)
dc.buzzerGpio = UInt32(buzzerGPIO)
dc.rebroadcastMode = RebroadcastModes(rawValue: rebroadcastMode)?.protoEnumValue() ?? RebroadcastModes.all.protoEnumValue()
dc.nodeInfoBroadcastSecs = UInt32(nodeInfoBroadcastSecs)
dc.doubleTapAsButtonPress = doubleTapAsButtonPress
2024-10-05 13:52:38 -07:00
dc.disableTripleClick = !tripleClickAsAdHocPing
2024-04-08 11:41:54 -07:00
dc.tzdef = tzdef
2024-04-26 18:06:23 -07:00
dc.ledHeartbeatDisabled = !ledHeartbeatEnabled
let adminMessageId = bleManager.saveDeviceConfig(config: dc, fromUser: connectedNode!.user!, toUser: node!.user!)
if adminMessageId > 0 {
// Should show a saved successfully alert once I know that to be true
// for now just disable the button after a successful save
hasChanges = false
goBack()
2022-06-21 02:43:37 -07:00
}
}
2022-09-23 21:41:07 -07:00
}
2022-06-13 20:43:51 -07:00
}
2022-06-20 00:13:04 -07:00
Spacer()
}
2025-05-08 20:34:25 -07:00
.navigationTitle("Device Config")
.navigationBarItems(
trailing: ZStack {
ConnectedDevice(
bluetoothOn: bleManager.isSwitchedOn,
deviceConnected: bleManager.connectedPeripheral != nil,
name: bleManager.connectedPeripheral?.shortName ?? "?"
)
}
)
.onFirstAppear {
// Need to request a DeviceConfig from the remote node before allowing changes
if let connectedPeripheral = bleManager.connectedPeripheral, let node {
let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context)
if let connectedNode {
if node.num != connectedNode.num {
if UserDefaults.enableAdministration {
/// 2.5 Administration with session passkey
let expiration = node.sessionExpiration ?? Date()
if expiration < Date() || node.deviceConfig == nil {
Logger.mesh.info("⚙️ Empty or expired device config requesting via PKI admin")
_ = bleManager.requestDeviceConfig(fromUser: connectedNode.user!, toUser: node.user!)
}
} else {
if node.deviceConfig == nil {
/// Legacy Administration
2025-05-13 06:19:27 -07:00
Logger.mesh.info("☠️ Using insecure legacy admin that is no longer supported, please upgrade your firmware.")
}
}
}
2023-01-31 22:08:03 -08:00
}
}
2022-06-21 02:43:37 -07:00
}
2024-10-05 13:52:38 -07:00
.onChange(of: deviceRole) { oldRole, newRole in
if oldRole != newRole && newRole != node?.deviceConfig?.role ?? -1 { hasChanges = true }
2022-06-21 02:43:37 -07:00
}
2024-10-05 13:52:38 -07:00
.onChange(of: buttonGPIO) { oldButtonGPIO, newButtonGPIO in
if oldButtonGPIO != newButtonGPIO && newButtonGPIO != node?.deviceConfig?.buttonGpio ?? -1 { hasChanges = true }
2022-12-05 19:47:56 -08:00
}
2024-10-05 13:52:38 -07:00
.onChange(of: buzzerGPIO) { oldBuzzerGPIO, newBuzzerGPIO in
if oldBuzzerGPIO != newBuzzerGPIO && newBuzzerGPIO != node?.deviceConfig?.buzzerGpio ?? -1 { hasChanges = true }
2022-12-05 19:47:56 -08:00
}
2024-10-05 13:52:38 -07:00
.onChange(of: rebroadcastMode) { oldRebroadcastMode, newRebroadcastMode in
if oldRebroadcastMode != newRebroadcastMode && newRebroadcastMode != node?.deviceConfig?.rebroadcastMode ?? -1 { hasChanges = true }
2023-04-01 15:01:11 -07:00
}
2024-10-06 08:50:12 -07:00
.onChange(of: nodeInfoBroadcastSecs) { oldNodeInfoBroadcastSecs, newNodeInfoBroadcastSecs in
if oldNodeInfoBroadcastSecs != newNodeInfoBroadcastSecs && newNodeInfoBroadcastSecs != node?.deviceConfig?.nodeInfoBroadcastSecs ?? -1 { hasChanges = true }
2024-02-12 22:09:22 -08:00
}
2024-10-06 08:50:12 -07:00
.onChange(of: doubleTapAsButtonPress) { oldDoubleTapAsButtonPress, newDoubleTapAsButtonPress in
if oldDoubleTapAsButtonPress != newDoubleTapAsButtonPress && newDoubleTapAsButtonPress != node?.deviceConfig?.doubleTapAsButtonPress ?? false { hasChanges = true }
2023-04-09 23:04:11 -07:00
}
2024-10-06 08:50:12 -07:00
.onChange(of: tripleClickAsAdHocPing) { oldTripleClickAsAdHocPing, newTripleClickAsAdHocPing in
if oldTripleClickAsAdHocPing != newTripleClickAsAdHocPing && newTripleClickAsAdHocPing != node?.deviceConfig?.tripleClickAsAdHocPing ?? false { hasChanges = true }
2024-10-04 19:36:30 -07:00
}
2024-10-06 08:50:12 -07:00
.onChange(of: tzdef) { oldTzdef, newTzdef in
if oldTzdef != newTzdef && newTzdef != node?.deviceConfig?.tzdef { hasChanges = true }
2024-04-08 11:41:54 -07:00
}
2024-10-06 08:50:12 -07:00
.onChange(of: ledHeartbeatEnabled) { oldLedHeartbeatEnabled, newLedHeartbeatEnabled in
if oldLedHeartbeatEnabled != newLedHeartbeatEnabled && newLedHeartbeatEnabled != node?.deviceConfig?.ledHeartbeatEnabled ?? false { hasChanges = true }
2024-08-08 12:16:14 -07:00
}
2022-06-13 20:43:51 -07:00
}
func setDeviceValues() {
2025-05-08 11:29:18 -07:00
if node?.deviceConfig?.role ?? 0 == 3 {
node?.deviceConfig?.role = 1
}
self.deviceRole = Int(node?.deviceConfig?.role ?? 0)
self.buttonGPIO = Int(node?.deviceConfig?.buttonGpio ?? 0)
self.buzzerGPIO = Int(node?.deviceConfig?.buzzerGpio ?? 0)
2023-04-01 15:01:11 -07:00
self.rebroadcastMode = Int(node?.deviceConfig?.rebroadcastMode ?? 0)
2024-02-12 22:09:22 -08:00
self.nodeInfoBroadcastSecs = Int(node?.deviceConfig?.nodeInfoBroadcastSecs ?? 900)
if nodeInfoBroadcastSecs < 3600 {
nodeInfoBroadcastSecs = 3600
}
2023-05-01 10:39:49 -07:00
self.doubleTapAsButtonPress = node?.deviceConfig?.doubleTapAsButtonPress ?? false
2024-10-05 13:52:38 -07:00
self.tripleClickAsAdHocPing = node?.deviceConfig?.tripleClickAsAdHocPing ?? false
2024-04-26 18:06:23 -07:00
self.ledHeartbeatEnabled = node?.deviceConfig?.ledHeartbeatEnabled ?? true
self.tzdef = node?.deviceConfig?.tzdef ?? ""
2025-07-16 23:12:47 -07:00
hasChanges = false
}
2022-06-13 20:43:51 -07:00
}