* Add Libraries for OTA

* Refactor BLE Manager a bit
* Add signal strength indicator graphic to ble connect view
* Order BLE devices by name
This commit is contained in:
Garth Vander Houwen 2022-12-04 00:28:26 -08:00
parent 405e52fd35
commit 6081d8c30c
10 changed files with 205 additions and 61 deletions

View file

@ -33,6 +33,8 @@
DD41582628582E9B009B0E59 /* DeviceConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD41582528582E9B009B0E59 /* DeviceConfig.swift */; };
DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD415827285859C4009B0E59 /* TelemetryConfig.swift */; };
DD41582A28585C32009B0E59 /* RangeTestConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD41582928585C32009B0E59 /* RangeTestConfig.swift */; };
DD457184293C55CD000C49FB /* NordicDFU in Frameworks */ = {isa = PBXBuildFile; productRef = DD457183293C55CD000C49FB /* NordicDFU */; };
DD457188293C7E63000C49FB /* SignalStrengthIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD457187293C7E63000C49FB /* SignalStrengthIndicator.swift */; };
DD47E3CE26F103C600029299 /* NodeList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3CD26F103C600029299 /* NodeList.swift */; };
DD47E3D626F17ED900029299 /* CircleText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3D526F17ED900029299 /* CircleText.swift */; };
DD47E3D926F3093800029299 /* MessageBubble.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3D826F3093800029299 /* MessageBubble.swift */; };
@ -148,6 +150,7 @@
DD41582528582E9B009B0E59 /* DeviceConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceConfig.swift; sourceTree = "<group>"; };
DD415827285859C4009B0E59 /* TelemetryConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryConfig.swift; sourceTree = "<group>"; };
DD41582928585C32009B0E59 /* RangeTestConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RangeTestConfig.swift; sourceTree = "<group>"; };
DD457187293C7E63000C49FB /* SignalStrengthIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalStrengthIndicator.swift; sourceTree = "<group>"; };
DD47E3CD26F103C600029299 /* NodeList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeList.swift; sourceTree = "<group>"; };
DD47E3D526F17ED900029299 /* CircleText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleText.swift; sourceTree = "<group>"; };
DD47E3D826F3093800029299 /* MessageBubble.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBubble.swift; sourceTree = "<group>"; };
@ -233,6 +236,7 @@
buildActionMask = 2147483647;
files = (
C9697FA527933B8C00250207 /* SQLite in Frameworks */,
DD457184293C55CD000C49FB /* NordicDFU in Frameworks */,
DD5394FC276993AD00AD86B1 /* SwiftProtobuf in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -517,6 +521,7 @@
DDB6ABDA28B0AC6000384BA1 /* DistanceText.swift */,
DD3CC6BD28E4CD9800FA9159 /* BatteryGauge.swift */,
DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */,
DD457187293C7E63000C49FB /* SignalStrengthIndicator.swift */,
);
path = Helpers;
sourceTree = "<group>";
@ -566,6 +571,7 @@
packageProductDependencies = (
DD5394FB276993AD00AD86B1 /* SwiftProtobuf */,
C9697FA427933B8C00250207 /* SQLite */,
DD457183293C55CD000C49FB /* NordicDFU */,
);
productName = MeshtasticClient;
productReference = DDC2E15426CE248E0042C5E4 /* Meshtastic.app */;
@ -643,6 +649,7 @@
packageReferences = (
DD5394FA276993AD00AD86B1 /* XCRemoteSwiftPackageReference "swift-protobuf" */,
C9697FA327933B8C00250207 /* XCRemoteSwiftPackageReference "SQLite.swift" */,
DD457182293C55CD000C49FB /* XCRemoteSwiftPackageReference "IOS-DFU-Library" */,
);
productRefGroup = DDC2E15526CE248E0042C5E4 /* Products */;
projectDirPath = "";
@ -707,6 +714,7 @@
buildActionMask = 2147483647;
files = (
DD4DED9027AD2975004BA27E /* cannedmessages.pb.swift in Sources */,
DD457188293C7E63000C49FB /* SignalStrengthIndicator.swift in Sources */,
DDCFF601285453A7005FA625 /* localonly.pb.swift in Sources */,
DD836AE726F6B38600ABCC23 /* Connect.swift in Sources */,
DDAF8C6E26ED19040058C060 /* Extensions.swift in Sources */,
@ -1153,6 +1161,14 @@
minimumVersion = 0.9.2;
};
};
DD457182293C55CD000C49FB /* XCRemoteSwiftPackageReference "IOS-DFU-Library" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/NordicSemiconductor/IOS-DFU-Library";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 4.0.0;
};
};
DD5394FA276993AD00AD86B1 /* XCRemoteSwiftPackageReference "swift-protobuf" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/apple/swift-protobuf.git";
@ -1169,6 +1185,11 @@
package = C9697FA327933B8C00250207 /* XCRemoteSwiftPackageReference "SQLite.swift" */;
productName = SQLite;
};
DD457183293C55CD000C49FB /* NordicDFU */ = {
isa = XCSwiftPackageProductDependency;
package = DD457182293C55CD000C49FB /* XCRemoteSwiftPackageReference "IOS-DFU-Library" */;
productName = NordicDFU;
};
DD5394FB276993AD00AD86B1 /* SwiftProtobuf */ = {
isa = XCSwiftPackageProductDependency;
package = DD5394FA276993AD00AD86B1 /* XCRemoteSwiftPackageReference "swift-protobuf" */;

View file

@ -1,5 +1,14 @@
{
"pins" : [
{
"identity" : "ios-dfu-library",
"kind" : "remoteSourceControl",
"location" : "https://github.com/NordicSemiconductor/IOS-DFU-Library",
"state" : {
"revision" : "ec5364755f4fcdf68d62ff4cf796d22e7b935f40",
"version" : "4.13.0"
}
},
{
"identity" : "sqlite.swift",
"kind" : "remoteSourceControl",
@ -17,6 +26,15 @@
"revision" : "e1499bc69b9040b29184f7f2996f7bab467c1639",
"version" : "1.19.0"
}
},
{
"identity" : "zipfoundation",
"kind" : "remoteSourceControl",
"location" : "https://github.com/weichsel/ZIPFoundation",
"state" : {
"revision" : "ec32d62d412578542c0ffb7a6ce34d3e64b43b94",
"version" : "0.9.11"
}
}
],
"version" : 2

View file

@ -7,9 +7,9 @@ import MapKit
// ---------------------------------------------------------------------------------------
// Meshtastic BLE Device Manager
// ---------------------------------------------------------------------------------------
class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeripheralDelegate {
class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
static let shared = BLEManager()
//static let shared = BLEManager()
private static var documentsFolder: URL {
do {
@ -25,19 +25,19 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
private var centralManager: CBCentralManager!
@Published var peripherals: [Peripheral]
@Published var peripherals: [Peripheral] = []
@Published var connectedPeripheral: Peripheral!
@Published var lastConnectionError: String
@Published var minimumVersion = "1.3.48"
@Published var connectedVersion: String
@Published var invalidVersion = false
@Published var preferredPeripheral = false
public var lastConnectionError: String
public var minimumVersion = "1.3.48"
public var connectedVersion: String
public var invalidVersion = false
public var preferredPeripheral = false
@Published var isSwitchedOn: Bool = false
@Published var isScanning: Bool = false
@Published var isConnecting: Bool = false
@Published var isConnected: Bool = false
@Published var isSubscribed: Bool = false
public var isSwitchedOn: Bool = false
public var isScanning: Bool = false
public var isConnecting: Bool = false
public var isConnected: Bool = false
public var isSubscribed: Bool = false
/// Used to make sure we never get foold by old BLE packets
private var configNonce: UInt32 = 1
@ -80,29 +80,16 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
self.lastConnectionError = ""
self.connectedVersion = "0.0.0"
self.peripherals = [Peripheral]()
super.init()
// let bleQueue: DispatchQueue = DispatchQueue(label: "CentralManager")
centralManager = CBCentralManager(delegate: self, queue: nil)
}
// MARK: Bluetooth enabled/disabled for the app
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
isSwitchedOn = true
startScanning()
} else {
isSwitchedOn = false
}
}
// MARK: Scanning for BLE Devices
// Scan for nearby BLE devices using the Meshtastic BLE service ID
func startScanning() {
if isSwitchedOn {
centralManager.scanForPeripherals(withServices: [meshtasticServiceCBUUID], options: nil)
centralManager.scanForPeripherals(withServices: [meshtasticServiceCBUUID], options: [CBCentralManagerScanOptionAllowDuplicatesKey: true])
DispatchQueue.main.async {
self.isScanning = self.centralManager.isScanning
}
@ -192,36 +179,6 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
}
// Called each time a peripheral is discovered
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
var peripheralName: String = peripheral.name ?? "Unknown"
if let name = advertisementData[CBAdvertisementDataLocalNameKey] as? String {
peripheralName = name
}
let newPeripheral = Peripheral(id: peripheral.identifier.uuidString, num: 0, name: peripheralName, shortName: "????", longName: peripheralName, firmwareVersion: "Unknown", rssi: RSSI.intValue, lastUpdate: Date(), peripheral: peripheral)
let peripheralIndex = peripherals.firstIndex(where: { $0.id == newPeripheral.id })
if peripheralIndex != nil && newPeripheral.peripheral.state != CBPeripheralState.connected {
peripherals[peripheralIndex!] = newPeripheral
peripherals.remove(at: peripheralIndex!)
peripherals.append(newPeripheral)
} else {
if newPeripheral.peripheral.state != CBPeripheralState.connected {
peripherals.append(newPeripheral)
}
}
let today = Date()
let visibleDuration = Calendar.current.date(byAdding: .second, value: -5, to: today)!
peripherals.removeAll(where: { $0.lastUpdate < visibleDuration})
}
// Called when a peripheral is connected
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
isConnecting = false
isConnected = true
@ -1608,3 +1565,56 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
return false
}
}
// MARK: - CB Central Manager implmentation
extension BLEManager: CBCentralManagerDelegate {
// MARK: Bluetooth enabled/disabled
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == CBManagerState.poweredOn {
print("BLE powered on")
isSwitchedOn = true
startScanning()
}
else {
isSwitchedOn = false
}
var status = ""
switch central.state {
case .poweredOff:
status = "BLE is powered off"
case .poweredOn:
status = "BLE is poweredOn"
case .resetting:
status = "BLE is resetting"
case .unauthorized:
status = "BLE is unauthorized"
case .unknown:
status = "BLE is unknown"
case .unsupported:
status = "BLE is unsupported"
default:
status = "default"
}
print("BLEManager status: \(status)")
}
// Called each time a peripheral is discovered
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
let name = advertisementData[CBAdvertisementDataLocalNameKey] as? String
let device = Peripheral(id: peripheral.identifier.uuidString, num: 0, name: name ?? "Unknown", shortName: "????", longName: name ?? "Unknown", firmwareVersion: "Unknown", rssi: RSSI.intValue, lastUpdate: Date(), peripheral: peripheral)
let index = peripherals.map { $0.peripheral }.firstIndex(of: peripheral)
if let peripheralIndex = index {
peripherals[peripheralIndex] = device
} else {
peripherals.append(device)
}
let today = Date()
let visibleDuration = Calendar.current.date(byAdding: .second, value: -5, to: today)!
self.peripherals.removeAll(where: { $0.lastUpdate < visibleDuration})
}
}

View file

@ -7,7 +7,7 @@ import CoreData
struct MeshtasticAppleApp: App {
let persistenceController = PersistenceController.shared
@ObservedObject private var bleManager: BLEManager = BLEManager.shared
@ObservedObject private var bleManager: BLEManager = BLEManager()
@ObservedObject private var userSettings: UserSettings = UserSettings()
@Environment(\.scenePhase) var scenePhase

View file

@ -23,4 +23,16 @@ struct Peripheral: Identifiable {
self.lastUpdate = lastUpdate
self.peripheral = peripheral
}
func getSignalStrength() -> SignalStrength {
if (NSNumber(value: rssi).compare(NSNumber(-65)) == ComparisonResult.orderedDescending) {
return SignalStrength.strong
}
else if (NSNumber(value: rssi).compare(NSNumber(-85)) == ComparisonResult.orderedDescending) {
return SignalStrength.normal
}
else {
return SignalStrength.weak
}
}
}

View file

@ -161,7 +161,7 @@ struct Connect: View {
if self.bleManager.isScanning {
Section(header: Text("Available Radios").font(.title)) {
ForEach(bleManager.peripherals.filter({ $0.peripheral.state == CBPeripheralState.disconnected }).sorted(by: { $0.rssi > $1.rssi })) { peripheral in
ForEach(bleManager.peripherals.filter({ $0.peripheral.state == CBPeripheralState.disconnected }).sorted(by: { $0.name > $1.name })) { peripheral in
HStack {
Image(systemName: "circle.fill")
.imageScale(.large).foregroundColor(.gray)
@ -184,7 +184,9 @@ struct Connect: View {
Text(peripheral.name).font(.title3)
}
Spacer()
Text(String(peripheral.rssi) + " dB").font(.title3)
VStack {
SignalStrengthIndicator(signalStrength: peripheral.getSignalStrength())
}
}.padding([.bottom, .top])
}
}.textCase(nil)

View file

@ -0,0 +1,78 @@
/*
* Copyright (c) 2022, Nordic Semiconductor
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this
* list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
import Foundation
import SwiftUI
struct SignalStrengthIndicator: View {
let signalStrength: SignalStrength
var body: some View {
HStack {
ForEach(0..<3) { bar in
RoundedRectangle(cornerRadius: 3)
.divided(amount: (CGFloat(bar) + 1) / CGFloat(3))
.fill(getColor().opacity(bar <= signalStrength.rawValue ? 1 : 0.3))
.frame(width: 8, height: 30)
}
}
}
private func getColor() -> Color {
switch signalStrength {
case .weak:
return Color.red
case .normal:
return Color.yellow
case .strong:
return Color.green
}
}
}
struct Divided<S: Shape>: Shape {
var amount: CGFloat // Should be in range 0...1
var shape: S
func path(in rect: CGRect) -> Path {
shape.path(in: rect.divided(atDistance: amount * rect.height, from: .maxYEdge).slice)
}
}
extension Shape {
func divided(amount: CGFloat) -> Divided<Self> {
return Divided(amount: amount, shape: self)
}
}
enum SignalStrength : Int {
case weak = 0
case normal = 1
case strong = 2
}

View file

@ -19,6 +19,7 @@ struct DeviceConfig: View {
@State var hasChanges = false
@State var deviceRole = 0
//@State var buzzerGPIO = 12
@State var serialEnabled = true
@State var debugLogEnabled = false
@ -135,6 +136,7 @@ struct DeviceConfig: View {
dc.role = DeviceRoles(rawValue: deviceRole)!.protoEnumValue()
dc.serialEnabled = serialEnabled
dc.debugLogEnabled = debugLogEnabled
//dc.buzzerGpio = UInt32(buzzerGPIO)
let adminMessageId = bleManager.saveDeviceConfig(config: dc, fromUser: node!.user!, toUser: node!.user!)

View file

@ -236,7 +236,7 @@ struct CannedMessagesConfig: View {
/// Can be e.g. "rotEnc1", "upDownEnc1", "cardkb", or keyword "_any"
cmc.allowInputSource = "rotEnc1"
} else if updown1Enabled {
cmc.allowInputSource = "_any"
cmc.allowInputSource = "upDown1"
} else {
cmc.allowInputSource = "_any"
}

View file

@ -142,6 +142,7 @@ struct ExternalNotificationConfig: View {
enc.active = active
enc.output = UInt32(output)
enc.outputMs = UInt32(outputMilliseconds)
enc.usePwm = usePWM
let adminMessageId = bleManager.saveExternalNotificationModuleConfig(config: enc, fromUser: node!.user!, toUser: node!.user!)
if adminMessageId > 0{
// Should show a saved successfully alert once I know that to be true