Device Config

This commit is contained in:
Garth Vander Houwen 2022-06-21 02:43:37 -07:00
parent 277d210532
commit c7b98cb7f4
6 changed files with 233 additions and 27 deletions

View file

@ -688,7 +688,8 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
positionPacket.longitudeI = Int32(LocationHelper.currentLocation.longitude * 1e7)
positionPacket.time = UInt32(LocationHelper.currentTimestamp.timeIntervalSince1970)
positionPacket.altitude = Int32(LocationHelper.currentAltitude)
positionPacket.groundSpeed = UInt32(LocationHelper.currentSpeed)
positionPacket.groundTrack = UInt32(LocationHelper.currentHeading)
var meshPacket = MeshPacket()
meshPacket.to = UInt32(destNum)
@ -850,6 +851,41 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
return false
}
public func saveDeviceConfig(config: Config.DeviceConfig, destNum: Int64, wantResponse: Bool) -> Bool {
var adminPacket = AdminMessage()
adminPacket.setConfig.device = config
var meshPacket: MeshPacket = MeshPacket()
meshPacket.to = UInt32(connectedPeripheral.num)
meshPacket.from = 0 //UInt32(connectedPeripheral.num)
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
meshPacket.priority = MeshPacket.Priority.reliable
meshPacket.wantAck = wantResponse
meshPacket.hopLimit = 0
var dataMessage = DataMessage()
dataMessage.payload = try! adminPacket.serializedData()
dataMessage.portnum = PortNum.adminApp
meshPacket.decoded = dataMessage
var toRadio: ToRadio!
toRadio = ToRadio()
toRadio.packet = meshPacket
let binaryData: Data = try! toRadio.serializedData()
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
return true
}
return false
}
public func saveLoRaConfig(config: Config.LoRaConfig, destNum: Int64, wantResponse: Bool) -> Bool {
var adminPacket = AdminMessage()

View file

@ -52,6 +52,79 @@ func localConfig (config: Config, meshlogging: Bool, context:NSManagedObjectCont
//
// print("🖥 Has Display config")
// }
if config.payloadVariant == Config.OneOf_PayloadVariant.device(config.device) {
var isDefault = false
if (try! config.device.jsonString()) == "{}" {
isDefault = true
}
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
do {
let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity]
// Found a node, save Device Config
if !fetchedNode.isEmpty {
if fetchedNode[0].deviceConfig == nil {
let newDeviceConfig = DeviceConfigEntity(context: context)
if isDefault {
// Client default protobuf value of 0
newDeviceConfig.role = 0
newDeviceConfig.serialEnabled = true
newDeviceConfig.debugLogEnabled = false
} else {
// Client default protobuf value of 0
newDeviceConfig.role = Int32(config.device.role.rawValue)
newDeviceConfig.serialEnabled = !config.device.serialDisabled
newDeviceConfig.debugLogEnabled = config.device.debugLogEnabled
}
fetchedNode[0].deviceConfig = newDeviceConfig
} else {
if isDefault {
// Client default protobuf value of 0
fetchedNode[0].deviceConfig?.role = 0
fetchedNode[0].deviceConfig?.serialEnabled = true
fetchedNode[0].deviceConfig?.debugLogEnabled = false
} else {
// Client default protobuf value of 0
fetchedNode[0].deviceConfig?.role = Int32(config.device.role.rawValue)
fetchedNode[0].deviceConfig?.serialEnabled = !config.device.serialDisabled
fetchedNode[0].deviceConfig?.debugLogEnabled = config.device.debugLogEnabled
}
}
do {
try context.save()
if meshlogging { MeshLogger.log("💾 Updated Device Config for node number: \(String(nodeNum))") }
} catch {
context.rollback()
let nsError = error as NSError
print("💥 Error Updating Core Data MyInfoEntity: \(nsError)")
}
}
} catch {
}
}
if config.payloadVariant == Config.OneOf_PayloadVariant.lora(config.lora) {
@ -119,14 +192,14 @@ func localConfig (config: Config, meshlogging: Bool, context:NSManagedObjectCont
do {
try context.save()
if meshlogging { MeshLogger.log("💾 Updated LoRaConfig for node number: \(String(nodeNum))") }
if meshlogging { MeshLogger.log("💾 Updated LoRa Config for node number: \(String(nodeNum))") }
} catch {
context.rollback()
let nsError = error as NSError
print("💥 Error Updating Core Data MyInfoEntity: \(nsError)")
print("💥 Error Updating Core Data LoRaConfigEntity: \(nsError)")
}
}

View file

@ -1,5 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="20086" systemVersion="21F79" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="DeviceConfigEntity" representedClassName="DeviceConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="debugLogEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="num" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="serialEnabled" optional="YES" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="deviceConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="deviceConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="LoRaConfigEntity" representedClassName="LoRaConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="hopLimit" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="modemPreset" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
@ -53,6 +60,7 @@
<attribute name="lastHeard" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<relationship name="deviceConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DeviceConfigEntity" inverseName="deviceConfigNode" inverseEntity="DeviceConfigEntity"/>
<relationship name="loRaConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="LoRaConfigEntity" inverseName="loRaConfigNode" inverseEntity="LoRaConfigEntity"/>
<relationship name="myInfo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="myInfoNode" inverseEntity="MyInfoEntity"/>
<relationship name="positions" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PositionEntity" inverseName="nodePosition" inverseEntity="PositionEntity"/>
@ -104,9 +112,10 @@
<element name="LoRaConfigEntity" positionX="45" positionY="144" width="128" height="104"/>
<element name="MessageEntity" positionX="-36" positionY="63" width="128" height="215"/>
<element name="MyInfoEntity" positionX="-18" positionY="81" width="128" height="209"/>
<element name="NodeInfoEntity" positionX="-63" positionY="-18" width="128" height="179"/>
<element name="NodeInfoEntity" positionX="-63" positionY="-18" width="128" height="194"/>
<element name="PositionEntity" positionX="-54" positionY="54" width="128" height="119"/>
<element name="TelemetryEntity" positionX="160" positionY="192" width="128" height="194"/>
<element name="UserEntity" positionX="0" positionY="144" width="128" height="200"/>
<element name="DeviceConfigEntity" positionX="45" positionY="144" width="128" height="104"/>
</elements>
</model>

View file

@ -20,29 +20,48 @@ enum DeviceRoles: Int, CaseIterable, Identifiable {
switch self {
case .client:
return "Client (default)"
return "Client (default) - App connected client."
case .clientMute:
return "Client Mute - Same as a client except packets will not hop over this node, does not contribute to routing packets for mesh."
case .router:
return "Router - Mesh packets will prefer to be routed over this node. This node will not be used by client apps. The wifi/ble radios and the oled screen will be put to sleep."
return "Router - Mesh packets will prefer to be routed over this node. This node will not be used by client apps. The wifi/ble radios and the oled screen will be put to sleep."
case .routerClient:
return "Router Client - Mesh packets will prefer to be routed over this node. The Router Client can be used as both a Router and an app connected Client."
}
}
}
func protoEnumValue() -> Config.DeviceConfig.Role {
switch self {
case .client:
return Config.DeviceConfig.Role.client
case .clientMute:
return Config.DeviceConfig.Role.clientMute
case .router:
return Config.DeviceConfig.Role.router
case .routerClient:
return Config.DeviceConfig.Role.routerClient
}
}
}
struct DeviceConfig: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
var node: NodeInfoEntity
@State private var isPresentingFactoryResetConfirm: Bool = false
@State private var isPresentingSaveConfirm: Bool = false
@State var initialLoad: Bool = true
@State var hasChanges = false
@State var deviceRole = 0
@State var serialEnabled = true
@State var debugLogEnabled = false
@State private var isPresentingFactoryResetConfirm: Bool = false
var body: some View {
VStack {
@ -77,24 +96,64 @@ struct DeviceConfig: View {
}
}
Button("Factory Reset", role: .destructive) {
HStack {
isPresentingFactoryResetConfirm = true
}
.disabled(bleManager.connectedPeripheral == nil)
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
.controlSize(.large)
.padding()
.confirmationDialog(
"Are you sure?",
isPresented: $isPresentingFactoryResetConfirm
) {
Button("Erase all device settings?", role: .destructive) {
Button {
isPresentingSaveConfirm = true
if !bleManager.sendFactoryReset(destNum: bleManager.connectedPeripheral.num, wantResponse: false) {
} label: {
Label("Save", systemImage: "square.and.arrow.down")
}
.disabled(bleManager.connectedPeripheral == nil || !hasChanges)
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
.controlSize(.large)
.padding()
.confirmationDialog(
"Are you sure?",
isPresented: $isPresentingSaveConfirm
) {
Button("Save Device Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") {
print("Factory Reset Failed")
var dc = Config.DeviceConfig()
dc.role = DeviceRoles(rawValue: deviceRole)!.protoEnumValue()
dc.serialDisabled = !serialEnabled
dc.debugLogEnabled = debugLogEnabled
if bleManager.saveDeviceConfig(config: dc, destNum: bleManager.connectedPeripheral.num, wantResponse: false) {
// 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
} else {
}
}
}
Button("Factory Reset", role: .destructive) {
isPresentingFactoryResetConfirm = true
}
.disabled(bleManager.connectedPeripheral == nil)
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
.controlSize(.large)
.padding()
.confirmationDialog(
"Are you sure?",
isPresented: $isPresentingFactoryResetConfirm
) {
Button("Erase all device settings?", role: .destructive) {
if !bleManager.sendFactoryReset(destNum: bleManager.connectedPeripheral.num, wantResponse: false) {
print("Factory Reset Failed")
}
}
}
}
@ -110,7 +169,37 @@ struct DeviceConfig: View {
})
.onAppear {
self.bleManager.context = context
if self.initialLoad{
self.bleManager.context = context
self.deviceRole = Int(node.deviceConfig?.role ?? 0)
self.serialEnabled = (node.deviceConfig?.serialEnabled ?? true)
self.debugLogEnabled = node.deviceConfig?.debugLogEnabled ?? false
self.hasChanges = false
self.initialLoad = false
}
}
.onChange(of: deviceRole) { newRole in
if newRole != node.deviceConfig!.role {
hasChanges = true
}
}
.onChange(of: serialEnabled) { newSerial in
if newSerial != node.deviceConfig!.serialEnabled {
hasChanges = true
}
}
.onChange(of: debugLogEnabled) { newDebugLog in
if newDebugLog != node.deviceConfig!.debugLogEnabled {
hasChanges = true
}
}
.navigationViewStyle(StackNavigationViewStyle())
}

View file

@ -287,7 +287,6 @@ struct LoRaConfig: View {
if self.initialLoad{
self.bleManager.context = context
print("got hops \(node.loRaConfig?.hopLimit ?? 0)")
self.hopLimit = Int(node.loRaConfig?.hopLimit ?? 0)
self.region = Int(node.loRaConfig?.regionCode ?? 0)
self.modemPreset = Int(node.loRaConfig?.modemPreset ?? 0)

View file

@ -48,7 +48,7 @@ struct Settings: View {
Section("Radio Configuration") {
NavigationLink {
DeviceConfig()
DeviceConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity())
} label: {
Image(systemName: "flipphone")