One working admin channel settings message

This commit is contained in:
Garth Vander Houwen 2023-01-19 22:04:18 -08:00
parent eb16c58f14
commit 79bc59adc9
7 changed files with 162 additions and 49 deletions

View file

@ -291,7 +291,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
}
}
func requestDeviceMetadata(fromUser: UserEntity, toUser: UserEntity, context: NSManagedObjectContext) -> Int64 {
func requestDeviceMetadata(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32, context: NSManagedObjectContext) -> Int64 {
guard (connectedPeripheral!.peripheral.state == CBPeripheralState.connected) else { return 0 }
@ -303,14 +303,16 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
adminPacket.getDeviceMetadataRequest = true
var meshPacket: MeshPacket = MeshPacket()
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
meshPacket.to = UInt32(toUser.num)
meshPacket.from = UInt32(fromUser.num)
meshPacket.priority = MeshPacket.Priority.reliable
meshPacket.wantAck = true
meshPacket.channel = UInt32(adminIndex)
var dataMessage = DataMessage()
dataMessage.payload = try! adminPacket.serializedData()
dataMessage.portnum = PortNum.adminApp
dataMessage.wantResponse = true
meshPacket.decoded = dataMessage
let messageDescription = "Requested Device Metadata for node \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown")) by \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))"
let messageDescription = "Requested Device Metadata for node \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown")) by \(fromUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))"
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
return Int64(meshPacket.id)
}
@ -1089,17 +1091,17 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
return false
}
public func saveUser(config: User, fromUser: UserEntity, toUser: UserEntity) -> Int64 {
public func saveUser(config: User, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 {
var adminPacket = AdminMessage()
adminPacket.setOwner = config
var meshPacket: MeshPacket = MeshPacket()
meshPacket.to = UInt32(connectedPeripheral.num)
meshPacket.from = 0 //UInt32(connectedPeripheral.num)
meshPacket.to = UInt32(toUser.num)
meshPacket.from = UInt32(fromUser.num)
meshPacket.channel = UInt32(adminIndex)
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
meshPacket.priority = MeshPacket.Priority.reliable
meshPacket.wantAck = true
meshPacket.hopLimit = 0
var dataMessage = DataMessage()
dataMessage.payload = try! adminPacket.serializedData()
dataMessage.portnum = PortNum.adminApp

View file

@ -763,6 +763,9 @@ func channelPacket (channel: Channel, fromNum: Int64, context: NSManagedObjectCo
mutableChannels.add(newChannel)
}
fetchedMyInfo[0].channels = mutableChannels.copy() as? NSOrderedSet
if newChannel.name?.lowercased() == "admin" {
fetchedMyInfo[0].adminIndex = newChannel.index
}
do {
try context.save()
} catch {
@ -780,6 +783,47 @@ func channelPacket (channel: Channel, fromNum: Int64, context: NSManagedObjectCo
}
}
func deviceMetadataPacket (metadata: DeviceMetadata, fromNum: Int64, context: NSManagedObjectContext) {
if metadata.isInitialized {
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.device.metadata.received %@", comment: "Device Metadata received from: %@"), String(fromNum))
MeshLogger.log("🎛️ \(logString)")
let fetchedNodeRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchedNodeRequest.predicate = NSPredicate(format: "num == %lld", fromNum)
do {
let fetchedNode = try context.fetch(fetchedNodeRequest) as! [NodeInfoEntity]
if fetchedNode.count == 1 {
let newMetadata = DeviceMetadataEntity(context: context)
newMetadata.firmwareVersion = metadata.firmwareVersion
newMetadata.deviceStateVersion = Int32(metadata.deviceStateVersion)
newMetadata.canShutdown = metadata.canShutdown
newMetadata.hasWifi = metadata.hasWifi_p
newMetadata.hasBluetooth = metadata.hasBluetooth_p
newMetadata.hasEthernet = metadata.hasEthernet_p
newMetadata.role = Int32(metadata.role.rawValue)
newMetadata.positionFlags = Int32(metadata.positionFlags)
fetchedNode[0].metadata = newMetadata
do {
try context.save()
} catch {
print("Failed to save device metadata")
}
print("💾 Updated Device Metadata from Admin App Packet For: \(fromNum)")
}
} catch {
context.rollback()
let nsError = error as NSError
print("💥 Error Saving MyInfo Channel from ADMIN_APP \(nsError)")
}
}
}
func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObjectContext) -> NodeInfoEntity? {
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.nodeinfo.received %@", comment: "Node info received for: %@"), String(nodeInfo.num))
@ -986,8 +1030,12 @@ func nodeInfoAppPacket (packet: MeshPacket, context: NSManagedObjectContext) {
func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) {
MeshLogger.log("🕸️ MESH PACKET received for Admin App \(try! packet.decoded.jsonString())")
if let adminMessage = try? AdminMessage(serializedData: packet.decoded.payload) {
MeshLogger.log("🕸️ MESH PACKET received for Admin App \(adminMessage.getDeviceMetadataResponse)")
if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getCannedMessageModuleMessagesResponse(adminMessage.getCannedMessageModuleMessagesResponse) {
if let cmmc = try? CannedMessageModuleConfig(serializedData: packet.decoded.payload) {
@ -1022,14 +1070,16 @@ func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) {
}
}
}
}
else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getChannelResponse(adminMessage.getChannelResponse) {
} else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getChannelResponse(adminMessage.getChannelResponse) {
channelPacket(channel: adminMessage.getChannelResponse, fromNum: Int64(packet.from), context: context)
} else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getDeviceMetadataResponse(adminMessage.getDeviceMetadataResponse) {
deviceMetadataPacket(metadata: adminMessage.getDeviceMetadataResponse, fromNum: Int64(packet.from), context: context)
print(try! adminMessage.getDeviceMetadataResponse.jsonString())
}
}
}
func positionPacket (packet: MeshPacket, context: NSManagedObjectContext) {

View file

@ -135,6 +135,7 @@
<relationship name="mqttConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="mqttConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="MyInfoEntity" representedClassName="MyInfoEntity" syncable="YES" codeGenerationType="class">
<attribute name="adminIndex" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="bitrate" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="bleName" optional="YES" attributeType="String"/>
<attribute name="errorCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>

View file

@ -98,7 +98,9 @@ struct MapViewSwiftUI: UIViewRepresentable {
self.parent = parent
super.init()
self.longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressHandler))
self.longPressRecognizer.minimumPressDuration = 0.3
self.longPressRecognizer.minimumPressDuration = 0.4
//self.longPressRecognizer.numberOfTouchesRequired = 1
//self.longPressRecognizer.cancelsTouchesInView = true
self.longPressRecognizer.delegate = self
self.parent.mapView.addGestureRecognizer(longPressRecognizer)
self.overlays = []
@ -163,13 +165,15 @@ struct MapViewSwiftUI: UIViewRepresentable {
let location = longPressRecognizer.location(in: self.parent.mapView)
// Map Coordinate - CLLocationCoordinate2D
let coordinate = self.parent.mapView.convert(location, toCoordinateFrom: self.parent.mapView)
parent.onLongPress(coordinate, 0)
// Add annotation:
let annotation = MKPointAnnotation()
annotation.title = "Dropped Pin"
annotation.coordinate = coordinate
parent.mapView.addAnnotation(annotation)
UINotificationFeedbackGenerator().notificationOccurred(.success)
if coordinate.longitude != LocationHelper.DefaultLocation.longitude && coordinate.latitude != LocationHelper.DefaultLocation.latitude {
let annotation = MKPointAnnotation()
annotation.title = "Dropped Pin"
annotation.coordinate = coordinate
parent.mapView.addAnnotation(annotation)
UINotificationFeedbackGenerator().notificationOccurred(.success)
parent.onLongPress(coordinate, 0)
}
}
public func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {

View file

@ -397,13 +397,12 @@ struct NodeDetail: View {
}
.navigationBarTitle(String(node.user?.longName ?? NSLocalizedString("unknown", comment: "")), displayMode: .inline)
.navigationBarItems(trailing:
ZStack {
ZStack {
ConnectedDevice(
bluetoothOn: bleManager.isSwitchedOn,
deviceConnected: bleManager.connectedPeripheral != nil,
name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
}
)
})
.onAppear {
self.bleManager.context = context
}

View file

@ -12,22 +12,59 @@ struct Settings: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@EnvironmentObject var userSettings: UserSettings
@FetchRequest(sortDescriptors: [NSSortDescriptor(key: "lastHeard", ascending: false)], animation: .default)
@FetchRequest(sortDescriptors: [NSSortDescriptor(key: "user.longName", ascending: true)], animation: .default)
private var nodes: FetchedResults<NodeInfoEntity>
@State private var selectedNode: Int = 0
@State private var connectedNodeNum: Int = 0
@State private var initialLoad: Bool = true
var body: some View {
NavigationSplitView {
List {
let connectedNodeNum = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.num : 0
NavigationLink() {
AppSettings()
} label: {
Image(systemName: "gearshape")
.symbolRenderingMode(.hierarchical)
Text("app.settings")
}
Section("Configure") {
Picker("Configuring Node", selection: $selectedNode) {
if connectedNodeNum == 0 {
Text("Connect to a Node").tag(0)
}
ForEach(nodes) { node in
if node.num == bleManager.connectedPeripheral?.num ?? 0 {
Text("BLE Config: \(node.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown"))")
.tag(Int(node.num))
} else if node.metadata != nil {
Text("Remote Config: \(node.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown"))")
.tag(Int(node.num))
} else {
Text("Request Admin: \(node.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown"))")
.tag(Int(node.num))
}
}
}
.pickerStyle(.menu)
.labelsHidden()
.onChange(of: selectedNode) { newValue in
if selectedNode > 0 {
let node = nodes.first(where: { $0.num == newValue })
let connectedNode = nodes.first(where: { $0.num == connectedNodeNum })
connectedNodeNum = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.num : 0)
if node?.metadata == nil && node!.num != connectedNodeNum {
let adminMessageId = bleManager.requestDeviceMetadata(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode!.myInfo!.adminIndex, context: context)
if adminMessageId > 0 {
print("Saved node metadata")
}
}
}
}
}
Section("radio.configuration") {
NavigationLink {
@ -37,85 +74,87 @@ struct Settings: View {
.symbolRenderingMode(.hierarchical)
Text("share.channels")
}
.disabled(selectedNode > 0 && selectedNode != connectedNodeNum)
NavigationLink {
UserConfig(node: nodes.first(where: { $0.num == connectedNodeNum }))
UserConfig(node: nodes.first(where: { $0.num == selectedNode }), connectedNode: nodes.first(where: { $0.num == connectedNodeNum }))
} label: {
Image(systemName: "person.crop.rectangle.fill")
.symbolRenderingMode(.hierarchical)
Text("user")
}
.disabled(selectedNode == 0)
NavigationLink() {
LoRaConfig(node: nodes.first(where: { $0.num == connectedNodeNum }))
LoRaConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "dot.radiowaves.left.and.right")
.symbolRenderingMode(.hierarchical)
Text("lora")
}
.disabled(selectedNode > 0 && selectedNode != connectedNodeNum)
NavigationLink() {
Channels(node: nodes.first(where: { $0.num == connectedNodeNum }))
} label: {
Image(systemName: "fibrechannel")
.symbolRenderingMode(.hierarchical)
Text("channels")
}
.disabled(selectedNode > 0 && selectedNode != connectedNodeNum)
NavigationLink() {
BluetoothConfig(node: nodes.first(where: { $0.num == connectedNodeNum }))
BluetoothConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "antenna.radiowaves.left.and.right")
.symbolRenderingMode(.hierarchical)
Text("bluetooth")
}
.disabled(selectedNode > 0 && selectedNode != connectedNodeNum)
NavigationLink {
DeviceConfig(node: nodes.first(where: { $0.num == connectedNodeNum }))
DeviceConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "flipphone")
.symbolRenderingMode(.hierarchical)
Text("device")
}
.disabled(selectedNode > 0 && selectedNode != connectedNodeNum)
NavigationLink {
DisplayConfig(node: nodes.first(where: { $0.num == connectedNodeNum }))
DisplayConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "display")
.symbolRenderingMode(.hierarchical)
Text("display")
}
.disabled(selectedNode > 0 && selectedNode != connectedNodeNum)
NavigationLink {
NetworkConfig(node: nodes.first(where: { $0.num == connectedNodeNum }))
NetworkConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "network")
.symbolRenderingMode(.hierarchical)
Text("network")
}
.disabled(selectedNode > 0 && selectedNode != connectedNodeNum)
NavigationLink {
PositionConfig(node: nodes.first(where: { $0.num == connectedNodeNum }))
PositionConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "location")
.symbolRenderingMode(.hierarchical)
Text("position")
}
.disabled(selectedNode > 0 && selectedNode != connectedNodeNum)
}
Section("module.configuration") {
NavigationLink {
CannedMessagesConfig(node: nodes.first(where: { $0.num == connectedNodeNum }))
CannedMessagesConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "list.bullet.rectangle.fill")
@ -123,42 +162,52 @@ struct Settings: View {
Text("canned.messages")
}
.disabled(selectedNode > 0 && selectedNode != connectedNodeNum)
NavigationLink {
ExternalNotificationConfig(node: nodes.first(where: { $0.num == connectedNodeNum }))
ExternalNotificationConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "megaphone")
.symbolRenderingMode(.hierarchical)
Text("external.notification")
}
.disabled(selectedNode > 0 && selectedNode != connectedNodeNum)
NavigationLink {
MQTTConfig(node: nodes.first(where: { $0.num == connectedNodeNum }))
MQTTConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "dot.radiowaves.right")
.symbolRenderingMode(.hierarchical)
Text("mqtt")
}
.disabled(selectedNode > 0 && selectedNode != connectedNodeNum)
NavigationLink {
RangeTestConfig(node: nodes.first(where: { $0.num == connectedNodeNum }))
RangeTestConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "point.3.connected.trianglepath.dotted")
.symbolRenderingMode(.hierarchical)
Text("range.test")
}
.disabled(selectedNode > 0 && selectedNode != connectedNodeNum)
NavigationLink {
SerialConfig(node: nodes.first(where: { $0.num == connectedNodeNum }))
SerialConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "terminal")
.symbolRenderingMode(.hierarchical)
Text("serial")
}
.disabled(selectedNode > 0 && selectedNode != connectedNodeNum)
NavigationLink {
TelemetryConfig(node: nodes.first(where: { $0.num == connectedNodeNum }))
TelemetryConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "chart.xyaxis.line")
.symbolRenderingMode(.hierarchical)
Text("telemetry")
}
.disabled(selectedNode > 0 && selectedNode != connectedNodeNum)
}
Section(header: Text("logging")) {
NavigationLink {
@ -169,7 +218,7 @@ struct Settings: View {
Text("mesh.log")
}
NavigationLink {
let connectedNode = nodes.first(where: { $0.num == connectedNodeNum })
let connectedNode = nodes.first(where: { $0.num == selectedNode })
AdminMessageList(user: connectedNode?.user)
} label: {
Image(systemName: "building.columns")
@ -191,6 +240,13 @@ struct Settings: View {
.onAppear {
self.bleManager.context = context
self.bleManager.userSettings = userSettings
if initialLoad {
selectedNode = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.num : 0)
initialLoad = false
}
connectedNodeNum = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.num : 0)
//let node = nodes.first(where: { $0.num == connectedNodeNum })
//selectedNode = node?.num ?? 0
}
.listStyle(GroupedListStyle())
.navigationTitle("settings")

View file

@ -13,6 +13,7 @@ struct UserConfig: View {
@Environment(\.dismiss) private var goBack
var node: NodeInfoEntity?
var connectedNode: NodeInfoEntity?
@State private var isPresentingFactoryResetConfirm: Bool = false
@State private var isPresentingSaveConfirm: Bool = false
@ -84,11 +85,11 @@ struct UserConfig: View {
isPresented: $isPresentingSaveConfirm,
titleVisibility: .visible
) {
Button("Save User Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") {
Button("Save User Config to \(node?.user?.longName ?? "Unknown")?") {
var u = User()
u.shortName = shortName
u.longName = longName
let adminMessageId = bleManager.saveUser(config: u, fromUser: node!.user!, toUser: node!.user!)
let adminMessageId = bleManager.saveUser(config: u, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
if adminMessageId > 0 {
hasChanges = false
goBack()