2022-06-13 20:43:51 -07:00
//
// D e v i c e C o n f i g . s w i f t
// M e s h t a s t i c A p p l e
//
// C o p y r i g h t ( c ) G a r t h V a n d e r H o u w e n 6 / 1 3 / 2 2 .
//
2024-06-07 22:09:20 -05:00
import MeshtasticProtobufs
2024-06-03 02:17:55 -07:00
import OSLog
2024-06-07 22:09:20 -05:00
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
2022-12-09 18:19:00 -08:00
@ Environment ( \ . dismiss ) private var goBack
2023-08-26 23:17:30 -07:00
2022-07-07 00:29:52 -07:00
var node : NodeInfoEntity ?
2023-08-26 23:17:30 -07:00
2022-10-03 21:19:10 -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
2025-03-04 21:38:35 -08:00
@ State private var pendingDeviceRole = 0
2022-12-05 19:47:56 -08:00
@ State var buzzerGPIO = 0
@ State var buttonGPIO = 0
2023-03-05 23:01:09 -08:00
@ 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
@ State private var confirmWarning = false
2022-06-13 20:43:51 -07:00
var body : some View {
2022-06-20 00:13:04 -07:00
VStack {
Form {
2024-02-17 22:39:22 -07:00
ConfigHeader ( title : " Device " , config : \ . deviceConfig , node : node , onAppear : setDeviceValues )
2025-04-26 16:05:09 -07:00
Section ( header : Text ( " Options " ) ) {
2024-02-21 20:41:27 -08:00
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 )
}
}
. onChange ( of : deviceRole ) { oldValue , newValue in
if ! confirmWarning {
if [ 2 , 11 ] . contains ( newValue ) {
pendingDeviceRole = newValue
deviceRole = oldValue // R e s e t s e l e c t i o n u n t i l c o n f i r m e d
showRouterWarning = true
}
} else {
confirmWarning = false
}
}
. confirmationDialog (
" Are you sure? " ,
isPresented : $ showRouterWarning ,
titleVisibility : . visible
) {
Button ( " Confirm " ) {
deviceRole = pendingDeviceRole
pendingDeviceRole = 0
confirmWarning = true
}
Button ( " Cancel " , role : . cancel ) {
pendingDeviceRole = 0
2024-02-21 20:41:27 -08:00
}
2025-03-04 21:38:35 -08:00
} message : {
Text ( " The Router roles are designed for high vantage locations like mountaintops and towers. This node needs to be able to have a good direct connection to most of the nodes on the network or else this will significantly hurt the network. " )
2022-06-13 20:43:51 -07:00
}
2024-02-21 20:41:27 -08:00
Text ( DeviceRoles ( rawValue : deviceRole ) ? . description ? ? " " )
. foregroundColor ( . gray )
2024-02-21 23:35:28 -08:00
. font ( . callout )
2022-06-13 20:43:51 -07:00
}
2024-02-21 23:35:28 -08:00
. pickerStyle ( DefaultPickerStyle ( ) )
2024-05-29 16:40:07 -05:00
2024-02-21 20:41:27 -08:00
VStack ( alignment : . leading ) {
Picker ( " Rebroadcast Mode " , selection : $ rebroadcastMode ) {
ForEach ( RebroadcastModes . allCases ) { rm in
Text ( rm . name )
}
2023-03-05 23:01:09 -08:00
}
2024-02-21 20:41:27 -08:00
Text ( RebroadcastModes ( rawValue : rebroadcastMode ) ? . description ? ? " " )
. foregroundColor ( . gray )
2024-02-21 23:35:28 -08:00
. font ( . callout )
2023-03-05 23:01:09 -08:00
}
2024-02-21 23:35:28 -08:00
. pickerStyle ( DefaultPickerStyle ( ) )
2024-05-29 16:40:07 -05:00
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 " ) ) {
2024-05-29 16:40:07 -05:00
2024-02-21 23:35:28 -08:00
Toggle ( isOn : $ doubleTapAsButtonPress ) {
Label ( " Double Tap as Button " , systemImage : " hand.tap " )
2024-02-21 20:41:27 -08:00
Text ( " Treat double tap on supported accelerometers as a user button press. " )
2023-04-09 23:04:11 -07:00
}
2024-02-21 23:35:28 -08:00
. toggleStyle ( SwitchToggleStyle ( tint : . accentColor ) )
2024-10-05 13:52:38 -07:00
2024-10-04 19:36:30 -07:00
Toggle ( isOn : $ tripleClickAsAdHocPing ) {
2024-11-29 13:15:46 -08:00
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-05-29 16:40:07 -05:00
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
}
2024-02-21 23:35:28 -08: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 " )
2024-04-09 13:04:09 -07:00
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
// O n l y m e s s w i t h t h e v a l u e i f i t i s t o o b i g
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-06-18 00:08:01 -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
}
2023-01-31 22:59:43 -08:00
. disabled ( self . bleManager . connectedPeripheral = = nil || node ? . deviceConfig = = nil )
2023-02-02 22:03:27 -08:00
// O n l y s h o w t h e s e b u t t o n s f o r t h e B L E c o n n e c t e d n o d e
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 )
2023-08-14 14:41:26 -07:00
. 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 ( )
2024-04-21 20:36:29 -07:00
clearCoreDataDatabase ( context : context , includeRoutes : false )
2023-11-20 16:08:46 -08:00
}
2024-05-29 16:40:07 -05: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 )
2023-08-14 14:41:26 -07:00
. padding ( . trailing )
2023-02-02 22:03:27 -08:00
. confirmationDialog (
2024-08-13 16:44:29 -07:00
" All device and app data will be deleted. " ,
2023-02-02 22:03:27 -08:00
isPresented : $ isPresentingFactoryResetConfirm ,
titleVisibility : . visible
) {
Button ( " Factory reset your device and app? " , role : . destructive ) {
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 ( )
2024-04-21 20:36:29 -07:00
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
}
}
}
}
2022-06-21 02:43:37 -07:00
HStack {
2024-02-18 00:03:34 -07:00
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
2024-02-18 00:03:34 -07:00
let adminMessageId = bleManager . saveDeviceConfig ( config : dc , fromUser : connectedNode ! . user ! , toUser : node ! . user ! , adminIndex : connectedNode ? . myInfo ? . adminIndex ? ? 0 )
if adminMessageId > 0 {
// S h o u l d s h o w a s a v e d s u c c e s s f u l l y a l e r t o n c e I k n o w t h a t t o b e t r u e
// f o r n o w j u s t d i s a b l e t h e b u t t o n a f t e r a s u c c e s s f u l s a v e
hasChanges = false
goBack ( )
2022-06-21 02:43:37 -07:00
}
2023-02-06 18:45:03 -08: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 " )
2024-08-11 09:07:22 -07:00
. navigationBarItems (
trailing : ZStack {
ConnectedDevice (
bluetoothOn : bleManager . isSwitchedOn ,
deviceConnected : bleManager . connectedPeripheral != nil ,
name : bleManager . connectedPeripheral ? . shortName ? ? " ? "
)
}
)
2024-09-04 10:06:34 -07:00
. onFirstAppear {
2024-08-11 09:07:22 -07:00
// N e e d t o r e q u e s t a D e v i c e C o n f i g f r o m t h e r e m o t e n o d e b e f o r e a l l o w i n g c h a n g e s
2024-09-04 10:06:34 -07:00
if let connectedPeripheral = bleManager . connectedPeripheral , let node {
let connectedNode = getNodeInfo ( id : connectedPeripheral . num , context : context )
if let connectedNode {
2024-09-05 19:31:29 -07:00
if node . num != connectedNode . num {
if UserDefaults . enableAdministration {
// / 2 . 5 A d m i n i s t r a t i o n w i t h s e s s i o n p a s s k e y
let expiration = node . sessionExpiration ? ? Date ( )
if expiration < Date ( ) || node . deviceConfig = = nil {
2024-11-29 13:15:46 -08:00
Logger . mesh . info ( " ⚙️ Empty or expired device config requesting via PKI admin " )
2024-09-05 19:31:29 -07:00
_ = bleManager . requestDeviceConfig ( fromUser : connectedNode . user ! , toUser : node . user ! , adminIndex : connectedNode . myInfo ? . adminIndex ? ? 0 )
}
} else {
if node . deviceConfig = = nil {
// / L e g a c y A d m i n i s t r a t i o n
2025-05-13 06:19:27 -07:00
Logger . mesh . info ( " ☠️ Using insecure legacy admin that is no longer supported, please upgrade your firmware. " )
2024-09-05 19:31:29 -07:00
}
2024-09-04 10:06:34 -07:00
}
}
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
}
2023-03-19 18:37:23 -07:00
func setDeviceValues ( ) {
2025-05-08 11:29:18 -07:00
if node ? . deviceConfig ? . role ? ? 0 = = 3 {
node ? . deviceConfig ? . role = 1
}
2023-03-19 18:37:23 -07:00
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 )
2024-03-17 09:07:11 -07:00
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
2024-05-04 13:02:47 -07:00
self . tzdef = node ? . deviceConfig ? . tzdef ? ? " "
2024-04-08 11:41:54 -07:00
if self . tzdef . isEmpty {
self . tzdef = TimeZone . current . posixDescription
self . hasChanges = true
} else {
self . hasChanges = false
}
2023-03-19 18:37:23 -07:00
}
2022-06-13 20:43:51 -07:00
}