2024-02-23 08:23:58 -08:00
import SwiftUI
2024-06-07 22:09:20 -05:00
import MeshtasticProtobufs
2024-09-04 10:27:06 -07:00
import OSLog
2024-02-23 08:23:58 -08:00
struct PowerConfig : View {
@ Environment ( \ . managedObjectContext ) private var context
@ EnvironmentObject private var bleManager : BLEManager
@ Environment ( \ . dismiss ) private var goBack
let node : NodeInfoEntity ?
@ State private var isPowerSaving = false
@ State private var shutdownOnPowerLoss = false
@ State private var shutdownAfterSecs = 0
@ State private var adcOverride = false
@ State private var adcMultiplier : Float = 0.0
@ State private var waitBluetoothSecs = 60
@ State private var lsSecs = 300
@ State private var minWakeSecs = 10
2024-05-29 16:40:07 -05:00
2024-02-23 08:23:58 -08:00
@ State private var currentDevice : DeviceHardware ?
2024-05-29 16:40:07 -05:00
2024-02-23 08:23:58 -08:00
@ State private var hasChanges : Bool = false
@ FocusState private var isFocused : Bool
var body : some View {
Form {
2025-05-08 22:50:44 -07:00
ConfigHeader ( title : " Power Config " , config : \ . powerConfig , node : node , onAppear : setPowerValues )
2024-02-23 08:23:58 -08:00
Section {
2024-05-29 16:40:07 -05:00
if ( currentDevice ? . architecture = = . esp32 || currentDevice ? . architecture = = . esp32S3 ) || ( currentDevice ? . architecture = = . nrf52840 && ( node ? . deviceConfig ? . role ? ? 0 = = 5 || node ? . deviceConfig ? . role ? ? 0 = = 6 ) ) {
2024-02-23 08:23:58 -08:00
Toggle ( isOn : $ isPowerSaving ) {
2025-05-08 14:42:11 -07:00
Label ( " Power Saving " , systemImage : " bolt " )
Text ( " Will sleep everything as much as possible, for the tracker and sensor role this will also include the lora radio. Don't use this setting if you want to use your device with the phone apps or are using a device without a user button. " )
2024-02-23 08:23:58 -08:00
}
. toggleStyle ( SwitchToggleStyle ( tint : . accentColor ) )
}
Toggle ( isOn : $ shutdownOnPowerLoss ) {
2025-05-08 22:50:44 -07:00
Label ( " Shutdown on Power Loss " , systemImage : " power " )
2024-02-23 08:23:58 -08:00
}
. toggleStyle ( SwitchToggleStyle ( tint : . accentColor ) )
if shutdownOnPowerLoss {
2025-05-03 08:58:33 -07:00
Picker ( " After " , selection : $ shutdownAfterSecs ) {
2024-02-23 08:23:58 -08:00
ForEach ( PowerIntervals . allCases ) { at in
Text ( at . description )
}
}
. pickerStyle ( DefaultPickerStyle ( ) )
}
} header : {
2025-04-27 16:19:10 -07:00
Text ( " Power " )
2024-02-23 08:23:58 -08:00
}
if currentDevice ? . architecture = = . esp32 || currentDevice ? . architecture = = . esp32S3 {
Section {
Toggle ( isOn : $ adcOverride ) {
2025-05-08 08:59:24 -07:00
Text ( " ADC Override " )
2024-02-23 08:23:58 -08:00
}
. toggleStyle ( SwitchToggleStyle ( tint : . accentColor ) )
if adcOverride {
HStack {
2025-05-08 08:59:24 -07:00
Text ( " Multiplier " )
2024-02-23 08:23:58 -08:00
Spacer ( )
2025-05-08 08:59:24 -07:00
FloatField ( title : " Multiplier " , number : $ adcMultiplier ) {
2024-02-23 08:23:58 -08:00
( 2.0 . . . 6.0 ) . contains ( $0 )
}
. focused ( $ isFocused )
Spacer ( )
}
}
} header : {
2025-05-08 08:59:24 -07:00
Text ( " Battery " )
2024-02-25 12:51:34 -08:00
}
2024-02-27 12:24:50 -08:00
// S e c t i o n {
// P i c k e r ( " c o n f i g . p o w e r . w a i t . b l u e t o o t h . s e c s " , s e l e c t i o n : $ w a i t B l u e t o o t h S e c s ) {
// F o r E a c h ( P o w e r I n t e r v a l s . a l l C a s e s ) {
// T e x t ( $ 0 . d e s c r i p t i o n )
// }
// }
// . p i c k e r S t y l e ( D e f a u l t P i c k e r S t y l e ( ) )
//
// P i c k e r ( " c o n f i g . p o w e r . l s . s e c s " , s e l e c t i o n : $ l s S e c s ) {
// F o r E a c h ( P o w e r I n t e r v a l s . a l l C a s e s ) {
// T e x t ( $ 0 . d e s c r i p t i o n )
// }
// }
// . p i c k e r S t y l e ( D e f a u l t P i c k e r S t y l e ( ) )
//
// P i c k e r ( " c o n f i g . p o w e r . m i n . w a k e . s e c s " , s e l e c t i o n : $ m i n W a k e S e c s ) {
// F o r E a c h ( P o w e r I n t e r v a l s . a l l C a s e s ) {
// T e x t ( $ 0 . d e s c r i p t i o n )
// }
// }
// . p i c k e r S t y l e ( D e f a u l t P i c k e r S t y l e ( ) )
//
// } h e a d e r : {
// T e x t ( " c o n f i g . p o w e r . s e c t i o n . s l e e p " )
// }
2024-02-23 08:23:58 -08:00
}
}
. disabled ( self . bleManager . connectedPeripheral = = nil || node ? . powerConfig = = nil )
2025-05-08 22:50:44 -07:00
. navigationTitle ( " Power Config " )
2024-02-23 08:23:58 -08:00
. navigationBarItems ( trailing : ZStack {
ConnectedDevice (
bluetoothOn : bleManager . isSwitchedOn ,
deviceConnected : bleManager . connectedPeripheral != nil ,
name : " \( bleManager . connectedPeripheral ? . shortName ? ? " ? " ) "
)
} )
. toolbar {
ToolbarItemGroup ( placement : . keyboard ) {
Spacer ( )
2025-02-15 11:28:28 -08:00
Button ( " Dismiss " ) {
2024-02-23 08:23:58 -08:00
isFocused = false
}
. font ( . subheadline )
}
}
2024-09-04 10:27:06 -07:00
. onFirstAppear {
2024-02-23 08:23:58 -08:00
Api ( ) . loadDeviceHardwareData { ( hw ) in
for device in hw {
let currentHardware = node ? . user ? . hwModel ? ? " UNSET "
let deviceString = device . hwModelSlug . replacingOccurrences ( of : " _ " , with : " " )
2024-05-29 16:40:07 -05:00
if deviceString = = currentHardware {
2024-02-23 08:23:58 -08:00
currentDevice = device
}
}
}
2024-09-04 10:27:06 -07:00
// N e e d t o r e q u e s t a N e t w o r k 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
if let connectedPeripheral = bleManager . connectedPeripheral , let node {
2024-11-29 13:15:46 -08:00
2024-09-04 10:27:06 -07:00
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 . powerConfig = = nil {
2024-11-29 13:15:46 -08:00
Logger . mesh . info ( " ⚙️ Empty or expired power config requesting via PKI admin " )
2025-06-14 14:25:46 -07:00
_ = bleManager . requestPowerConfig ( fromUser : connectedNode . user ! , toUser : node . user ! )
2024-09-05 19:31:29 -07:00
}
} else {
// / 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-04 10:27:06 -07:00
}
}
2024-02-23 08:23:58 -08:00
}
}
}
2024-10-05 16:35:42 -07:00
. onChange ( of : isPowerSaving ) { oldIsPowerSaving , newIsPowerSaving in
if oldIsPowerSaving != newIsPowerSaving && newIsPowerSaving != node ? . powerConfig ? . isPowerSaving { hasChanges = true }
2024-02-23 08:23:58 -08:00
}
2024-10-05 16:35:42 -07:00
. onChange ( of : shutdownOnPowerLoss ) { _ , newShutdownOnPowerLoss in
2024-08-11 17:31:27 -07:00
if newShutdownOnPowerLoss {
hasChanges = true
}
2024-02-23 08:23:58 -08:00
}
2024-10-05 16:35:42 -07:00
. onChange ( of : shutdownAfterSecs ) { oldShutdownAfterSecs , newShutdownAfterSecs in
if oldShutdownAfterSecs != newShutdownAfterSecs && newShutdownAfterSecs != node ? . powerConfig ? . minWakeSecs ? ? - 1 { hasChanges = true }
2024-02-23 08:23:58 -08:00
}
2024-10-05 16:35:42 -07:00
. onChange ( of : adcOverride ) {
2024-02-23 08:23:58 -08:00
hasChanges = true
}
2024-10-05 16:35:42 -07:00
. onChange ( of : adcMultiplier ) { _ , newAdcMultiplier in
if newAdcMultiplier != node ? . powerConfig ? . adcMultiplierOverride ? ? - 1 { hasChanges = true }
2024-02-23 08:23:58 -08:00
}
2024-10-05 16:35:42 -07:00
. onChange ( of : waitBluetoothSecs ) { oldWaitBluetoothSecs , newWaitBluetoothSecs in
if oldWaitBluetoothSecs != newWaitBluetoothSecs && newWaitBluetoothSecs != node ? . powerConfig ? . waitBluetoothSecs ? ? - 1 { hasChanges = true }
2024-02-23 08:23:58 -08:00
}
2024-10-06 08:50:12 -07:00
. onChange ( of : lsSecs ) { _ , newLsSecs in
if newLsSecs != node ? . powerConfig ? . lsSecs ? ? - 1 { hasChanges = true }
2024-02-23 08:23:58 -08:00
}
2024-10-06 08:50:12 -07:00
. onChange ( of : minWakeSecs ) { _ , newMinWakeSecs in
if newMinWakeSecs != node ? . powerConfig ? . minWakeSecs ? ? - 1 { hasChanges = true }
2024-02-23 08:23:58 -08:00
}
SaveConfigButton ( node : node , hasChanges : $ hasChanges ) {
guard let connectedNode = getNodeInfo ( id : bleManager . connectedPeripheral . num , context : context ) ,
let fromUser = connectedNode . user ,
let toUser = node ? . user else {
return
}
var config = Config . PowerConfig ( )
config . isPowerSaving = isPowerSaving
config . onBatteryShutdownAfterSecs = shutdownOnPowerLoss ? UInt32 ( shutdownAfterSecs ) : 0
config . adcMultiplierOverride = adcOverride ? adcMultiplier : 0
config . waitBluetoothSecs = UInt32 ( waitBluetoothSecs )
config . lsSecs = UInt32 ( lsSecs )
config . minWakeSecs = UInt32 ( minWakeSecs )
let adminMessageId = bleManager . savePowerConfig (
config : config ,
fromUser : fromUser ,
2025-06-14 14:25:46 -07:00
toUser : toUser
2024-02-23 08:23:58 -08:00
)
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 ( )
}
}
}
private func setPowerValues ( ) {
isPowerSaving = node ? . powerConfig ? . isPowerSaving ? ? isPowerSaving
shutdownAfterSecs = Int ( node ? . powerConfig ? . onBatteryShutdownAfterSecs ? ? Int32 ( shutdownAfterSecs ) )
shutdownOnPowerLoss = shutdownAfterSecs != 0
adcMultiplier = node ? . powerConfig ? . adcMultiplierOverride ? ? adcMultiplier
adcOverride = adcMultiplier != 0
waitBluetoothSecs = Int ( node ? . powerConfig ? . waitBluetoothSecs ? ? Int32 ( waitBluetoothSecs ) )
lsSecs = Int ( node ? . powerConfig ? . lsSecs ? ? Int32 ( lsSecs ) )
minWakeSecs = Int ( node ? . powerConfig ? . minWakeSecs ? ? Int32 ( minWakeSecs ) )
}
}
// / H e l p e r v i e w f o r i s o l a t i n g u s e r f l o a t i n p u t t h a t c a n b e v a l i d a t e d b e f o r e b e i n g a p p l i e d .
private struct FloatField : View {
let title : String
@ Binding var number : Float
var isValid : ( Float ) -> Bool = { _ in true }
@ State private var typingNumber : Float = 0.0
var body : some View {
TextField ( title . localized , value : $ typingNumber , format : . number )
. foregroundColor ( . gray )
. multilineTextAlignment ( . trailing )
2024-10-05 15:50:57 -07:00
. onChange ( of : typingNumber ) {
2024-02-23 08:23:58 -08:00
if isValid ( typingNumber ) {
number = typingNumber
} else {
typingNumber = number
}
2024-10-05 15:50:57 -07:00
}
2024-02-23 08:23:58 -08:00
. keyboardType ( . decimalPad )
. onAppear {
typingNumber = number
}
}
}