Clean up null values on details pages, add some enhanced bluetooth cleanup

This commit is contained in:
Garth Vander Houwen 2021-10-01 08:33:11 -07:00
parent 4247d2a8aa
commit 22bbd04db5
9 changed files with 228 additions and 71 deletions

View file

@ -19,6 +19,8 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
@Published var lastConnectedNode: String
//private var rssiArray = [NSNumber]()
private var timer = Timer()
private var broadcastNodeId: UInt32 = 4294967295
@Published var isSwitchedOn = false
@Published var peripherals = [Peripheral]()
@ -51,47 +53,96 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
isSwitchedOn = false
}
}
//---------------------------------------------------------------------------------------
// Scan for nearby BLE devices using the Meshtastic BLE service ID
//---------------------------------------------------------------------------------------
/*
* Scan for nearby BLE devices using the Meshtastic BLE service ID
*/
func startScanning() {
// Remove Existing Data
peripherals.removeAll()
peripheralArray.removeAll()
//rssiArray.removeAll()
// Start Scanning
print("Start Scanning")
centralManager.scanForPeripherals(withServices: [meshtasticServiceCBUUID])
}
//---------------------------------------------------------------------------------------
// Stop Scanning For BLE Devices
//---------------------------------------------------------------------------------------
func stopScanning() {
print("Stop Scanning")
self.centralManager.stopScan()
print("Scanning Stopped")
}
//---------------------------------------------------------------------------------------
// Connect to a Device via UUID
//---------------------------------------------------------------------------------------
func connectToDevice(id: String) {
cleanup()
connectedPeripheral = peripheralArray.filter({ $0.identifier.uuidString == id }).first
connectedNodeInfo = Peripheral(id: connectedPeripheral.identifier.uuidString, name: connectedPeripheral.name ?? "Unknown", rssi: 0, myInfo: nil)
lastConnectedNode = id
self.centralManager?.connect(connectedPeripheral!)
}
//---------------------------------------------------------------------------------------
// Disconnect Device function
//---------------------------------------------------------------------------------------
/*
* Disconnect Device function
*/
func disconnectDevice(){
if connectedPeripheral != nil {
self.centralManager?.cancelPeripheralConnection(connectedPeripheral!)
}
cleanup()
}
/*
* Send Broadcast Message
*/
public func sendMessage(message: String) -> Bool
{ var success = true
if connectedPeripheral == nil || connectedPeripheral!.state != CBPeripheralState.connected {
success = false
}
else {
let messageModel = MessageModel(messageId: 0, messageTimeStamp: UInt32(Date().timeIntervalSince1970), fromUserId: self.connectedNode.id, toUserId: broadcastNodeId, fromUserLongName: self.connectedNode.user.longName, toUserLongName: "Broadcast", fromUserShortName: self.connectedNode.user.shortName, toUserShortName: "BC", receivedACK: false, messagePayload: message, direction: "OUT")
let dataType = PortNum.textMessageApp
let payloadData: Data = message.data(using: String.Encoding.utf8)!
var dataMessage = DataMessage()
dataMessage.payload = payloadData
dataMessage.portnum = dataType
var meshPacket = MeshPacket()
meshPacket.to = broadcastNodeId
meshPacket.decoded = dataMessage
meshPacket.wantAck = true
var toRadio: ToRadio!
toRadio = ToRadio()
toRadio.packet = meshPacket
let binaryData: Data = try! toRadio.serializedData()
if (connectedPeripheral!.state == CBPeripheralState.connected)
{
connectedPeripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
messageData.messages.append(messageModel)
messageData.save()
}
else
{
connectToDevice(id: lastConnectedNode)
}
}
return success
}
//---------------------------------------------------------------------------------------
// Set Owner function
@ -114,12 +165,45 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
}
}
//---------------------------------------------------------------------------------------
// Discover Peripheral Event
//---------------------------------------------------------------------------------------
/*
* Call this when things either go wrong, or you're done with the connection.
* This cancels any subscriptions if there are any, or straight disconnects if not.
* (didUpdateNotificationStateForCharacteristic will cancel the connection if a subscription is involved)
*/
private func cleanup() {
// Don't do anything if we're not connected
guard let connectedPeripheral = connectedPeripheral,
case .connected = connectedPeripheral.state else { return }
for service in (connectedPeripheral.services ?? [] as [CBService]) {
for characteristic in (service.characteristics ?? [] as [CBCharacteristic]) {
if characteristic.uuid == FROMNUM_UUID && characteristic.isNotifying {
// It is notifying, so unsubscribe
self.connectedPeripheral?.setNotifyValue(false, for: characteristic)
}
}
}
centralManager.cancelPeripheralConnection(connectedPeripheral)
}
/*
* This callback happens whenever a peripheral that is advertising the Meshtastic Service UUID is found.
* We check the RSSI, to make sure it's close enough that we're interested in it, and if it is,
* we start the connection process
*/
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print(peripheral)
// Reject if the signal strength if it is too low
guard RSSI.intValue >= -100
else {
print("Discovered perhiperal not in expected range, at %d", RSSI.intValue)
return
}
print("Discovered %s at %d", String(describing: peripheral.name), RSSI.intValue)
if peripheralArray.contains(peripheral) {
print("Duplicate Found.")
} else {
@ -154,6 +238,25 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
self.startScanning()
}
/*
* If the connection fails for whatever reason, we need to deal with it.
*/
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
// print("Failed to connect to \(String(describing: peripheral.name!))", peripheral, String(describing: error))
// let errorCode = (error! as! NSError).userInfo
// print("Central disconnected because \(errorCode)")
if let e = error {
let errorDetails = (e as NSError)
print("Central disconnected because \(errorDetails.localizedDescription)")
} else {
print("Central disconnected! (no error)")
}
cleanup()
}
//---------------------------------------------------------------------------------------
// Disconnect Peripheral Event
//---------------------------------------------------------------------------------------
@ -168,9 +271,9 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
}
if(peripheral.identifier == connectedPeripheral.identifier){
connectedPeripheral = nil
connectedNodeInfo = nil
connectedNode = nil
// connectedPeripheral = nil
// connectedNodeInfo = nil
// connectedNode = nil
}
print("Peripheral disconnected: " + peripheral.name!)
self.startScanning()
@ -237,16 +340,23 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
}
}
//---------------------------------------------------------------------------------------
// Data Read / Update Characteristic Event
//---------------------------------------------------------------------------------------
/*
* Callback lets us know that data has arrived via a notification on the characteristic
*/
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?)
{
// Handle Error
if let error = error {
print("Error discovering characteristics: %s", error.localizedDescription)
cleanup()
return
}
switch characteristic.uuid
{
case FROMNUM_UUID:
peripheral.readValue(for: FROMRADIO_characteristic)
print(characteristic.value ?? "no value")
case FROMRADIO_UUID:
if (characteristic.value == nil || characteristic.value!.isEmpty)
{
@ -282,6 +392,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
connectedNode = meshData.nodes.first(where: {$0.id == decodedInfo.myInfo.myNodeNum})
if connectedNode != nil {
connectedNode.myInfo = myInfoModel
connectedNode.update(from: connectedNode.data)
}
meshData.save()
@ -297,9 +408,19 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
if meshData.nodes.contains(where: {$0.id == decodedInfo.nodeInfo.num}) {
let nodeIndex = meshData.nodes.firstIndex(where: { $0.id == decodedInfo.nodeInfo.num })
meshData.nodes.remove(at: nodeIndex!)
meshData.save()
// Found a matching node lets update it
let nodeMatch = meshData.nodes.first(where: { $0.id == decodedInfo.nodeInfo.num })
if nodeMatch?.lastHeard ?? 0 > decodedInfo.nodeInfo.lastHeard {
let nodeIndex = meshData.nodes.firstIndex(where: { $0.id == decodedInfo.nodeInfo.num })
meshData.nodes.remove(at: nodeIndex!)
meshData.save()
}
else {
// Data is older than what the app already has
return
}
}
meshData.nodes.append(
@ -346,7 +467,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
let fromUser = meshData.nodes.first(where: { $0.id == decodedInfo.packet.from })
var toUserLongName: String = "Broadcast"
var toUserShortName: String = "BRD"
var toUserShortName: String = "BC"
if decodedInfo.packet.to != broadcastNodeId {
@ -356,7 +477,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
}
messageData.messages.append(
MessageModel(messageId: decodedInfo.packet.id, messageTimeStamp: Int64(decodedInfo.packet.rxTime), fromUserId: decodedInfo.packet.from, toUserId: decodedInfo.packet.to, fromUserLongName: fromUser?.user.longName ?? "Unknown", toUserLongName: toUserLongName, fromUserShortName: fromUser?.user.shortName ?? "???", toUserShortName: toUserShortName, receivedACK: decodedInfo.packet.decoded.wantResponse, messagePayload: messageText, direction: "IN"))
MessageModel(messageId: decodedInfo.packet.id, messageTimeStamp: decodedInfo.packet.rxTime, fromUserId: decodedInfo.packet.from, toUserId: decodedInfo.packet.to, fromUserLongName: fromUser?.user.longName ?? "Unknown", toUserLongName: toUserLongName, fromUserShortName: fromUser?.user.shortName ?? "???", toUserShortName: toUserShortName, receivedACK: decodedInfo.packet.decoded.wantResponse, messagePayload: messageText, direction: "IN"))
messageData.save()
} else {

View file

@ -10,7 +10,7 @@ struct MessageModel : Identifiable, Codable
{
let id: UUID
var messageId: UInt32
var messageTimestamp: Int64
var messageTimestamp: UInt32
var fromUserId: UInt32
var toUserId: UInt32
var fromUserLongName: String
@ -21,7 +21,7 @@ struct MessageModel : Identifiable, Codable
var messagePayload: String
var direction: String
init(id: UUID = UUID(), messageId: UInt32, messageTimeStamp: Int64, fromUserId: UInt32, toUserId: UInt32, fromUserLongName: String, toUserLongName: String, fromUserShortName: String, toUserShortName: String, receivedACK: Bool, messagePayload: String, direction: String)
init(id: UUID = UUID(), messageId: UInt32, messageTimeStamp: UInt32, fromUserId: UInt32, toUserId: UInt32, fromUserLongName: String, toUserLongName: String, fromUserShortName: String, toUserShortName: String, receivedACK: Bool, messagePayload: String, direction: String)
{
self.id = id
self.messageId = messageId
@ -52,7 +52,7 @@ extension MessageModel {
struct Data {
var id: UUID
var messageId: UInt32
var messageTimestamp: Int64
var messageTimestamp: UInt32
var fromUserId: UInt32
var toUserId: UInt32
var fromUserLongName: String

View file

@ -26,7 +26,7 @@ struct Connect: View {
List {
Section(header: Text("Connected Device").font(.title)) {
if(bleManager.connectedPeripheral != nil){
if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.state == .connected {
HStack {
Image(systemName: "antenna.radiowaves.left.and.right")
.symbolRenderingMode(.hierarchical)

View file

@ -9,7 +9,7 @@ struct BatteryIcon: View {
if batteryLevel == 100 {
Image(systemName: "battery.100")
Image(systemName: "battery.100.bolt")
.font(font)
.foregroundColor(color)
.symbolRenderingMode(.hierarchical)
@ -35,6 +35,13 @@ struct BatteryIcon: View {
.foregroundColor(color)
.symbolRenderingMode(.hierarchical)
}
else if batteryLevel! == 0 {
Image(systemName: "powerplug")
.font(font)
.foregroundColor(color)
.symbolRenderingMode(.hierarchical)
}
else {
Image(systemName: "battery.0")

View file

@ -6,8 +6,11 @@ struct MessageBubble: View {
var time: Int32
var shortName: String
var body: some View {
HStack (alignment: .top) {
CircleText(text: shortName, color: isCurrentUser ? Color.blue : Color(.darkGray)).padding(.all, 5)
@ -19,10 +22,16 @@ struct MessageBubble: View {
.background(isCurrentUser ? Color.blue : Color(.darkGray))
.cornerRadius(10)
HStack (spacing: 4) {
let messageDate = Date(timeIntervalSince1970: TimeInterval(time))
let messageDate = Date(timeIntervalSince1970: TimeInterval(time))
Text(messageDate, style: .date).font(.caption2).foregroundColor(.gray)
Text(messageDate, style: .time).font(.caption2).foregroundColor(.gray)
if time != 0 {
Text(messageDate, style: .date).font(.caption2).foregroundColor(.gray)
Text(messageDate, style: .time).font(.caption2).foregroundColor(.gray)
}
else {
Text("Unknown").font(.caption2).foregroundColor(.gray)
}
}
.padding(.bottom, 10)
}

View file

@ -86,7 +86,12 @@ struct Messages: View {
.overlay(RoundedRectangle(cornerRadius: 20).stroke(.tertiary, lineWidth: 1))
.padding(.bottom, 15)
Button(action: sendMessage) {
Button(action: {
if bleManager.sendMessage(message: typingMessage) {
typingMessage = ""
}
} ) {
Image(systemName: "arrow.up.circle.fill").font(.largeTitle).foregroundColor(.blue)
}
@ -128,7 +133,3 @@ struct Messages: View {
}
}
}
func sendMessage() {
//chatHelper.sendMessage(Message(content: typingMessage, user: DataSource.secondUser))
// typingMessage = ""
}

View file

@ -36,7 +36,7 @@ struct NodeDetail: View {
let annotations = [MapLocation(name: node.user.shortName, coordinate: node.position.coordinate!)]
Map(coordinateRegion: regionBinding, showsUserLocation: true, userTrackingMode: .constant(.none), annotationItems: annotations) { location in
Map(coordinateRegion: regionBinding, showsUserLocation: true, userTrackingMode: .none, annotationItems: annotations) { location in
MapAnnotation(
coordinate: location.coordinate,
content: {
@ -63,26 +63,34 @@ struct NodeDetail: View {
}
.padding([.leading, .trailing, .bottom])
Divider()
VStack(alignment: .center) {
Image(systemName: "waveform.path")
.font(.title)
.foregroundColor(.blue)
.symbolRenderingMode(.hierarchical)
Text("SNR").font(.title2).fixedSize()
Text(String(node.snr ?? 0))
.font(.title2)
.foregroundColor(.gray)
if node.snr! > 0 {
VStack(alignment: .center) {
Image(systemName: "waveform.path")
.font(.title)
.foregroundColor(.blue)
.symbolRenderingMode(.hierarchical)
Text("SNR").font(.title2).fixedSize()
Text(String(node.snr ?? 0))
.font(.title2)
.foregroundColor(.gray)
}
Divider()
}
Divider()
VStack(alignment: .center) {
BatteryIcon(batteryLevel: node.position.batteryLevel, font: .title, color: Color.blue)
Text("Battery").font(.title2).fixedSize()
Text(String(node.position.batteryLevel!) + "%")
.font(.title2)
.foregroundColor(.gray)
.symbolRenderingMode(.hierarchical)
}
if node.position.batteryLevel! > 0 {
Text("Battery").font(.title2).fixedSize()
Text(String(node.position.batteryLevel!) + "%")
.font(.title2)
.foregroundColor(.gray)
.symbolRenderingMode(.hierarchical)
}
else {
Text("Powered").font(.title2)
}
}
}.padding(4)
Divider()
HStack {
@ -97,15 +105,21 @@ struct NodeDetail: View {
}
.padding()
Divider()
HStack{
if node.lastHeard > 0 {
Image(systemName: "clock").font(.title2).foregroundColor(.blue)
let lastHeard = Date(timeIntervalSince1970: TimeInterval(node.lastHeard))
Text("Last Heard:").font(.title3)
Text(lastHeard, style: .relative).font(.title3)
Text("ago").font(.title3)
}.padding()
Divider()
HStack{
Image(systemName: "clock").font(.title2).foregroundColor(.blue)
let lastHeard = Date(timeIntervalSince1970: TimeInterval(node.lastHeard))
//Text("Last Heard:").font(.title3)
//Text(lastHeard, style: .relative).font(.title3)
//Text("ago").font(.title3)
Text("Last Heard: \(lastHeard, style: .relative) ago").font(.title3)
}.padding()
Divider()
}
if node.position.coordinate != nil {
HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/, spacing: 14) {
Image(systemName: "mappin").font(.title).foregroundColor(.blue)

View file

@ -29,7 +29,7 @@ struct NodeMap: View {
}
var body: some View {
let location = LocationHelper.currentLocation
let location = LocationHelper.currentLocation
let currentCoordinatePosition = CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude)
let regionBinding = Binding<MKCoordinateRegion>(
get: {

View file

@ -17,9 +17,14 @@ struct NodeRow: View {
HStack (alignment: .bottom){
Image(systemName: "timer").font(.headline).foregroundColor(.blue).symbolRenderingMode(.hierarchical)
let lastHeard = Date(timeIntervalSince1970: TimeInterval(node.lastHeard))
Text("Last Heard:").font(.headline).foregroundColor(.gray)
Text(lastHeard, style: .relative).font(.headline).foregroundColor(.gray)
if node.lastHeard > 0 {
let lastHeard = Date(timeIntervalSince1970: TimeInterval(node.lastHeard))
Text("Last Heard: \(lastHeard, style: .relative) ago").font(.subheadline).foregroundColor(.gray)
}
else {
Text("Last Heard: Unknown").font(.subheadline).foregroundColor(.gray)
}
}
}.padding([.leading, .top, .bottom])
}