Added detection sensor config view and core data

This commit is contained in:
Ben Meadors 2023-08-17 16:30:48 -05:00
parent 9a5579a2cf
commit 70014a7b3f
5 changed files with 260 additions and 16 deletions

View file

@ -1534,6 +1534,32 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
return 0
}
public func saveDetectionSensorModuleConfig(config: ModuleConfig.DetectionSensorConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 {
var adminPacket = AdminMessage()
adminPacket.setModuleConfig.detectionSensor = config
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.channel = UInt32(adminIndex)
meshPacket.priority = MeshPacket.Priority.reliable
meshPacket.wantAck = true
var dataMessage = DataMessage()
dataMessage.payload = try! adminPacket.serializedData()
dataMessage.portnum = PortNum.adminApp
meshPacket.decoded = dataMessage
let messageDescription = "Saved Telemetry Module Config for \(toUser.longName ?? "unknown".localized)"
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
upsertDetectionSensorModuleConfigPacket(config: config, nodeNum: toUser.num, context: context!)
return Int64(meshPacket.id)
}
return 0
}
public func getChannel(channelIndex: UInt32, fromUser: UserEntity, toUser: UserEntity, wantResponse: Bool) -> Bool {
var adminPacket = AdminMessage()
@ -1902,6 +1928,34 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
return false
}
public func requestDetectionSensorModuleConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool {
var adminPacket = AdminMessage()
adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.detectionsensorConfig
var meshPacket: MeshPacket = MeshPacket()
meshPacket.to = UInt32(toUser.num)
meshPacket.from = UInt32(fromUser.num)
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
meshPacket.priority = MeshPacket.Priority.reliable
meshPacket.channel = UInt32(adminIndex)
meshPacket.wantAck = true
var dataMessage = DataMessage()
dataMessage.payload = try! adminPacket.serializedData()
dataMessage.portnum = PortNum.adminApp
dataMessage.wantResponse = true
meshPacket.decoded = dataMessage
let messageDescription = "🛎️ Requested Detection Sensor Module Config on admin channel \(adminIndex) for node: \(String(connectedPeripheral.num))"
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
return true
}
return false
}
public func requestSerialModuleConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool {
var adminPacket = AdminMessage()

View file

@ -40,7 +40,14 @@
</uniquenessConstraints>
</entity>
<entity name="DetectionSensorConfigEntity" representedClassName="DetectionSensorConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="detectionTriggeredHigh" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="minimumBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="monitorPin" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="name" optional="YES" attributeType="String"/>
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="stateBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="usePullup" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<relationship name="detectionSensorConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="detectionSensorConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="DeviceConfigEntity" representedClassName="DeviceConfigEntity" syncable="YES" codeGenerationType="class">

View file

@ -972,3 +972,64 @@ func upsertTelemetryModuleConfigPacket(config: Meshtastic.ModuleConfig.Telemetry
print("💥 Fetching node for core data TelemetryConfigEntity failed: \(nsError)")
}
}
func upsertDetectionSensorModuleConfigPacket(config: Meshtastic.ModuleConfig.DetectionSensorConfig, nodeNum: Int64, context: NSManagedObjectContext) {
let logString = String.localizedStringWithFormat("mesh.log.detectionsensor.config %@".localized, String(nodeNum))
MeshLogger.log("📈 \(logString)")
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
do {
guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else {
return
}
// Found a node, save Detection Sensor Config
if !fetchedNode.isEmpty {
if fetchedNode[0].detectionSensorConfig == nil {
let newConfig = DetectionSensorConfigEntity(context: context)
newConfig.enabled = config.enabled
newConfig.sendBell = config.sendBell
newConfig.name = config.name
newConfig.monitorPin = Int32(config.monitorPin)
newConfig.detectionTriggeredHigh = config.detectionTriggeredHigh
newConfig.usePullup = config.usePullup
newConfig.minimumBroadcastSecs = Int32(config.minimumBroadcastSecs)
newConfig.stateBroadcastSecs = Int32(config.stateBroadcastSecs)
fetchedNode[0].detectionSensorConfig = newConfig
} else {
fetchedNode[0].detectionSensorConfig?.enabled = config.enabled
fetchedNode[0].detectionSensorConfig?.sendBell = config.sendBell
fetchedNode[0].detectionSensorConfig?.name = config.name
fetchedNode[0].detectionSensorConfig?.monitorPin = Int32(config.monitorPin)
fetchedNode[0].detectionSensorConfig?.usePullup = config.usePullup
fetchedNode[0].detectionSensorConfig?.detectionTriggeredHigh = config.detectionTriggeredHigh
fetchedNode[0].detectionSensorConfig?.minimumBroadcastSecs = Int32(config.minimumBroadcastSecs)
fetchedNode[0].detectionSensorConfig?.stateBroadcastSecs = Int32(config.stateBroadcastSecs)
}
do {
try context.save()
print("💾 Updated Detection Sensor Module Config for node number: \(String(nodeNum))")
} catch {
context.rollback()
let nsError = error as NSError
print("💥 Error Updating Core Data DetectionSensorConfigEntity: \(nsError)")
}
} else {
print("💥 No Nodes found in local database matching node number \(nodeNum) unable to save Detection Sensor Module Config")
}
} catch {
let nsError = error as NSError
print("💥 Fetching node for core data DetectionSensorConfigEntity failed: \(nsError)")
}
}

View file

@ -15,6 +15,14 @@ struct DetectionSensorConfig: View {
@State private var isPresentingSaveConfirm: Bool = false
@State var hasChanges: Bool = false
@State var enabled = false
/// DetectionSensorModule will sends a bell character with the messages.
@State var sendBell: Bool = false
@State var name: String = ""
@State var detectionTriggeredHigh: Bool = true
@State var usePullup: Bool = false
@State var minimumBroadcastSecs = UInt32(0)
@State var stateBroadcastSecs = UInt32(0)
@State var monitorPin = UInt32(0)
var body: some View {
@ -26,7 +34,7 @@ struct DetectionSensorConfig: View {
} else if node != nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 {
// Let users know what is going on if they are using remote admin and don't have the config yet
if node?.mqttConfig == nil {
if node?.detectionSensorConfig == nil {
Text("Detection Sensor config data was requested over the admin channel but no response has been returned from the remote node. You can check the status of admin message requests in the admin message log.")
.font(.callout)
.foregroundColor(.orange)
@ -49,10 +57,72 @@ struct DetectionSensorConfig: View {
Toggle(isOn: $enabled) {
Label("enabled", systemImage: "dot.radiowaves.right")
}
Toggle(isOn: $sendBell) {
Label("Send Bell", systemImage: "bell")
}
TextField("Friendly name (sent for detection alerts text messages)", text: $name, axis: .vertical)
.foregroundColor(.gray)
.autocapitalization(.none)
.disableAutocorrection(true)
.onChange(of: name, perform: { _ in
let totalBytes = name.utf8.count
// Only mess with the value if it is too big
if totalBytes > 20 {
let firstNBytes = Data(name.utf8.prefix(20))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the shortName back to the last place where it was the right size
name = maxBytesString
}
}
})
.foregroundColor(.gray)
}
Section(header: Text("Sensor option")) {
Picker("GPIO Pin to monitor", selection: $monitorPin) {
ForEach(0..<46) {
if $0 == 0 {
Text("unset")
} else {
Text("Pin \($0)")
}
}
}
.pickerStyle(DefaultPickerStyle())
Toggle(isOn: $detectionTriggeredHigh) {
Label("Detection trigger High", systemImage: "dial.high")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $usePullup) {
Label("Uses pullup resistor", systemImage: "arrow.up.to.line")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
}
Section(header: Text("update.interval")) {
Text("Mininum time between detection broadcasts. Default is 45 seconds.")
.font(.caption)
Picker("Minimum time between detection broadcasts", selection: $minimumBroadcastSecs) {
ForEach(UpdateIntervals.allCases) { ui in
Text(ui.description)
}
}
.pickerStyle(DefaultPickerStyle())
Text("How often to send detection sensor state to mesh regardless of detection. Default is Never.")
.font(.caption)
Picker("State Broadcast Interval", selection: $stateBroadcastSecs) {
ForEach(UpdateIntervals.allCases) { ui in
Text(ui.description)
}
}
.pickerStyle(DefaultPickerStyle())
}
}
.scrollDismissesKeyboard(.interactively)
.disabled(self.bleManager.connectedPeripheral == nil || node?.mqttConfig == nil)
.disabled(self.bleManager.connectedPeripheral == nil || node?.detectionSensorConfig == nil)
Button {
isPresentingSaveConfirm = true
@ -74,22 +144,29 @@ struct DetectionSensorConfig: View {
let nodeName = node?.user?.longName ?? "unknown".localized
let buttonText = String.localizedStringWithFormat("save.config %@".localized, nodeName)
Button(buttonText) {
var dsc = DetectionSensorConfig()
var dsc = ModuleConfig.DetectionSensorConfig()
dsc.enabled = self.enabled
// let adminMessageId = bleManager.saveMQTTConfig(config: mqtt, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
// 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()
// }
}
dsc.sendBell = self.sendBell
dsc.name = self.name
dsc.monitorPin = UInt32(self.monitorPin)
dsc.detectionTriggeredHigh = self.detectionTriggeredHigh
dsc.usePullup = self.usePullup
dsc.minimumBroadcastSecs = UInt32(self.minimumBroadcastSecs)
dsc.stateBroadcastSecs = UInt32(self.stateBroadcastSecs)
let adminMessageId = bleManager.saveDetectionSensorModuleConfig(config: dsc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
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()
} }
}
}
message: {
Text("config.save.confirm")
}
.navigationTitle("mqtt.config")
.navigationTitle("detection.sensor.config")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
@ -98,12 +175,12 @@ struct DetectionSensorConfig: View {
self.bleManager.context = context
setDetectionSensorValues()
// Need to request a TelemetryModuleConfig from the remote node before allowing changes
if bleManager.connectedPeripheral != nil && node?.mqttConfig == nil {
print("empty mqtt module config")
// Need to request a Detection Sensor Module Config from the remote node before allowing changes
if bleManager.connectedPeripheral != nil && node?.detectionSensorConfig == nil {
print("empty detection sensor module config")
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
if node != nil && connectedNode != nil {
_ = bleManager.requestMqttModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
_ = bleManager.requestDetectionSensorModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
}
}
}
@ -112,10 +189,53 @@ struct DetectionSensorConfig: View {
if newEnabled != node!.detectionSensorConfig!.enabled { hasChanges = true }
}
}
.onChange(of: sendBell) { newSendBell in
if node != nil && node?.detectionSensorConfig != nil {
if newSendBell != node!.detectionSensorConfig!.sendBell { hasChanges = true }
}
}
.onChange(of: detectionTriggeredHigh) { newDetectionTriggeredHigh in
if node != nil && node?.detectionSensorConfig != nil {
if newDetectionTriggeredHigh != node!.detectionSensorConfig!.detectionTriggeredHigh { hasChanges = true }
}
}
.onChange(of: usePullup) { newUsePullup in
if node != nil && node?.detectionSensorConfig != nil {
if newUsePullup != node!.detectionSensorConfig!.usePullup { hasChanges = true }
}
}
.onChange(of: name) { newName in
if node != nil && node?.detectionSensorConfig != nil {
if newName != node!.detectionSensorConfig!.name { hasChanges = true }
}
}
.onChange(of: monitorPin) { newMonitorPin in
if node != nil && node?.detectionSensorConfig != nil {
if newMonitorPin != node!.detectionSensorConfig!.monitorPin { hasChanges = true }
}
}
.onChange(of: minimumBroadcastSecs) { newMinimumBroadcastSecs in
if node != nil && node?.detectionSensorConfig != nil {
if newMinimumBroadcastSecs != node!.detectionSensorConfig!.minimumBroadcastSecs { hasChanges = true }
}
}
.onChange(of: stateBroadcastSecs) { newStateBroadcastSecs in
if node != nil && node?.detectionSensorConfig != nil {
if newStateBroadcastSecs != node!.detectionSensorConfig!.stateBroadcastSecs { hasChanges = true }
}
}
}
func setDetectionSensorValues() {
self.enabled = (node?.detectionSensorConfig?.enabled ?? false)
self.sendBell = (node?.detectionSensorConfig?.sendBell ?? false)
self.name = (node?.detectionSensorConfig?.name ?? "")
self.monitorPin = UInt32(node?.detectionSensorConfig?.monitorPin ?? 0)
self.usePullup = (node?.detectionSensorConfig?.usePullup ?? false)
self.detectionTriggeredHigh = (node?.detectionSensorConfig?.detectionTriggeredHigh ?? true)
self.minimumBroadcastSecs = UInt32(node?.detectionSensorConfig?.minimumBroadcastSecs ?? 45)
self.stateBroadcastSecs = UInt32(node?.detectionSensorConfig?.stateBroadcastSecs ?? 0)
self.hasChanges = false
}
}

View file

@ -57,6 +57,8 @@
"current"="Current";
"default"="Default";
"delete"="Delete";
"detection.sensor"="Detection Sensor";
"detection.sensor.config"="Detection Sensor Config";
"device"="Device";
"device.config"="Device Config";
"device.metrics.delete"="Delete all device metrics?";