Skip to content

Commit eb7da8c

Browse files
committed
- Finally added support for reconnecting to a device that wasn't explicitly disconnected by the user
- Set application characteristics to include permission to run in the background to maintain current BLE connections, and reconnect to the device if it disconnects unexpectedly - Added functionality to the autoconnect option: rather than grabbing the first device named InfiniTime, the user can manually connect to a device, and then press a button in the settings to save the UUID of the connected device. On app open, if autoconnect is enabled, scan for devices until it sees that UUID, then connect immediately.
1 parent cfe950b commit eb7da8c

7 files changed

Lines changed: 85 additions & 46 deletions

File tree

Infini-iOS/BLE/BLEDelegates.swift

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,29 +27,13 @@ extension BLEManager: CBPeripheralDelegate {
2727
if characteristic.properties.contains(.read) {
2828
peripheral.readValue(for: characteristic)
2929
}
30-
// MARK: - Subscribe to services
31-
// subscribe to HRM, battery, and music control characteristics
32-
if characteristic.properties.contains(.notify) {
33-
/*switch characteristic.uuid {
34-
case musicControlCBUUID:
35-
//peripheral.setNotifyValue(true, for: characteristic)
36-
case hrmCBUUID:
37-
//peripheral.setNotifyValue(true, for: characteristic)
38-
case batCBUUID:
39-
//peripheral.setNotifyValue(true, for: characteristic)
40-
break
41-
default:
42-
break
43-
}
44-
peripheral.setNotifyValue(true, for: characteristic)*/
45-
print(characteristic.uuid, " can notify")
46-
}
4730

48-
if characteristic.properties.contains(.write) {
49-
if characteristic.uuid == notifyCBUUID {
50-
// I'm sure there's a less clunky way to grab the full characteristic for the sendNotification() function, but this works fine for now
51-
notifyCharacteristic = characteristic
31+
if characteristic.uuid == notifyCBUUID {
32+
// I'm sure there's a less clunky way to grab the full characteristic for the sendNotification() function, but this works fine for now
33+
notifyCharacteristic = characteristic
34+
if firstConnect {
5235
sendNotification(notification: "iOS Connected!")
36+
firstConnect = false
5337
}
5438
}
5539
}

Infini-iOS/BLE/BLEManager.swift

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate {
2020
var artist: CBCharacteristic!
2121
}
2222

23+
struct Peripheral: Identifiable {
24+
let id: Int
25+
let name: String
26+
let rssi: Int
27+
let peripheralHash: Int
28+
let deviceUUID: CBUUID
29+
}
30+
2331
@Published var musicChars = musicCharacteristics()
2432

2533
let settings = UserDefaults.standard
@@ -29,14 +37,18 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate {
2937
@Published var isScanning = false // another UI flag. Probably not necessary for anything but debugging. I dunno maybe a little swirly animation or something could be triggered by this
3038
@Published var isConnectedToPinetime = false // another flag published to update UI stuff. Can probably be implemented better in the future
3139
@Published var heartBPM: String = "Disconnected" // published var to communicate the HRM data to the UI.
32-
@Published var batteryLevel: String = "20"//"Disconnected" // Same as heartBPM but for battery data
40+
@Published var batteryLevel: String = "Disconnected" // Same as heartBPM but for battery data
3341
@Published var firmwareVersion: String = "Disconnected"
3442

3543
// Selecting and connecting variables
3644
@Published var peripherals = [Peripheral]() // used to print human-readable device names to UI in selection process
3745
@Published var deviceToConnect: Int! // When the user selects a device from the UI, that peripheral's ID goes in this var, which is passed to the peripheralDictionary
3846
@Published var peripheralDictionary: [Int: CBPeripheral] = [:] // this is the dictionary that relates human-readable peripheral names to the CBPeripheral class that CoreBluetooth actually interacts with
3947
@Published var infiniTime: CBPeripheral! // variable to save the CBPeripheral that you're connecting to
48+
@Published var autoconnectPeripheral: CBPeripheral!
49+
@Published var setAutoconnectUUID: String = "" // placeholder for now while I figure out how to save the whole device in UserDefaults to save "favorite" devices
50+
51+
var firstConnect: Bool = true // makes iOS connected message only show up on first connect, not if device drops connection and reconnects
4052

4153
// declare some CBUUIDs for easier reference
4254
let hrmCBUUID = CBUUID(string: "2A37")
@@ -48,12 +60,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate {
4860
let musicTrackCBUUID = CBUUID(string: "00000004-78FC-48FE-8E23-433B3A1942D0")
4961
let musicArtistCBUUID = CBUUID(string: "00000003-78FC-48FE-8E23-433B3A1942D0")
5062

51-
struct Peripheral: Identifiable {
52-
let id: Int
53-
let name: String
54-
let rssi: Int
55-
let peripheralHash: Int
56-
}
63+
5764

5865
override init() {
5966
super.init()
@@ -78,6 +85,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate {
7885
func disconnect(){
7986
if infiniTime != nil {
8087
myCentral.cancelPeripheralConnection(infiniTime)
88+
firstConnect = true
8189
}
8290
}
8391

@@ -92,6 +100,19 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate {
92100
infiniTime.delegate = self
93101
self.myCentral.connect(peripheral, options: nil)
94102

103+
setAutoconnectUUID = peripheral.identifier.uuidString
104+
isConnectedToPinetime = true
105+
}
106+
}
107+
func autoconnect() {
108+
if autoconnectPeripheral != nil {
109+
self.myCentral.stopScan()
110+
isScanning = false
111+
112+
self.infiniTime = autoconnectPeripheral
113+
infiniTime.delegate = self
114+
self.myCentral.connect(autoconnectPeripheral, options: nil)
115+
95116
isConnectedToPinetime = true
96117
}
97118
}
@@ -109,35 +130,50 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate {
109130
- the hash I'm using is only unique between PineTimes because the peripheral struct includes an incrementing ID number that's part of the hash.
110131
- this works for getting more than one PT in the menu, but is obviously a drag because it's not at all guaranteed to be persistent
111132
- there's some stuff happening here that doesn't need to happen - ex. there's an array and a dictionary doing basically the same thing?
133+
- I mistakenly was under the impression that the UUID was generated by InfiniTime and was probably the same across all instances of InfiniTime, but it seems like that's incorrect!
112134
*/
113135

114136
if let name = advertisementData[CBAdvertisementDataLocalNameKey] as? String {
115137
peripheralName = name
116-
let newPeripheral = Peripheral(id: peripheralDictionary.count, name: peripheralName, rssi: RSSI.intValue, peripheralHash: peripheral.hash)
138+
let devUUIDString: String = peripheral.identifier.uuidString
139+
//setAutoconnectUUID = devUUIDString
140+
let devUUID: CBUUID = CBUUID(string: devUUIDString)
141+
let newPeripheral = Peripheral(id: peripheralDictionary.count, name: peripheralName, rssi: RSSI.intValue, peripheralHash: peripheral.hash, deviceUUID: devUUID)
142+
143+
print(peripheralName + ": " + String(peripheral.identifier.uuidString))
117144

118-
let autoconnect = UserDefaults.standard.object(forKey: "autoconnect") as? Bool ?? true
145+
// handle autoconnect defaults
146+
let settings = UserDefaults.standard
147+
let autoconnect = settings.object(forKey: "autoconnect") as? Bool ?? true
148+
let autoconnectUUID = settings.object(forKey: "autoconnectUUID") as! String
119149

120-
if autoconnect {
150+
if autoconnect && devUUIDString == autoconnectUUID {
121151
connect(peripheral: peripheral)
122152
}
123153
else {
124154
// compare peripheral hashes to make sure we're only adding each device once -- super helpful if you have a very noisy BLE advertiser nearby!
125-
if !peripherals.contains(where: {$0.peripheralHash == newPeripheral.peripheralHash}) {
155+
if !peripherals.contains(where: {$0.deviceUUID == newPeripheral.deviceUUID}) {
126156
peripherals.append(newPeripheral)
127157
peripheralDictionary[newPeripheral.peripheralHash] = peripheral
128158

129-
print(newPeripheral, "added to list")
159+
//print(newPeripheral, "added to list")
130160
}
131161
}
132162
}
133163
}
134164

165+
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
166+
print(error ?? "no error")
167+
}
168+
135169
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
136170
self.infiniTime.discoverServices(nil)
137171
}
138172

139173
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
140-
isConnectedToPinetime = false
174+
if error != nil {
175+
connect(peripheral: peripheral)
176+
}
141177
}
142178

143179
func centralManagerDidUpdateState(_ central: CBCentralManager) {

Infini-iOS/Info.plist

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,19 @@
2424
<string>This app needs permission to allow your PineTime to control your music.</string>
2525
<key>NSBluetoothAlwaysUsageDescription</key>
2626
<string>This app uses bluetooth to communicate with your PineTime.</string>
27+
<key>NSBluetoothPeripheralUsageDescription</key>
28+
<string>This app uses bluetooth to communicate with your PineTime.</string>
2729
<key>UIApplicationSceneManifest</key>
2830
<dict>
2931
<key>UIApplicationSupportsMultipleScenes</key>
3032
<true/>
3133
</dict>
3234
<key>UIApplicationSupportsIndirectInputEvents</key>
3335
<true/>
36+
<key>UIBackgroundModes</key>
37+
<array>
38+
<string>bluetooth-central</string>
39+
</array>
3440
<key>UILaunchScreen</key>
3541
<dict/>
3642
<key>UIRequiredDeviceCapabilities</key>

Infini-iOS/Settings/SettingsView.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ struct Settings_Page: View {
1717
@AppStorage("watchNotifications") var watchNotifications: Bool = true
1818
@AppStorage("autoconnect") var autoconnect: Bool = false
1919
@AppStorage("batteryNotification") var batteryNotification: Bool = false
20+
@AppStorage("autoconnectUUID") var autoconnectUUID: String = "empty"
2021

2122

2223
var body: some View {
@@ -28,8 +29,15 @@ struct Settings_Page: View {
2829
Section(header: Text("Connection")) {
2930
Toggle("Autoconnect to Nearest PineTime", isOn: $autoconnect)
3031
if autoconnect {
31-
Text("Warning! Autoconnect is insecure! Only use in safe environments.").foregroundColor(Color.red)
32+
Text("Autoconnect is currently experimental! Connection is made based on device UUID, which I am only sort of sure is static.").foregroundColor(Color.red)
3233
}
34+
Button {
35+
autoconnectUUID = bleManager.setAutoconnectUUID
36+
print(autoconnectUUID)
37+
} label: {
38+
Text("Select Current Device for Autoconnect")
39+
}
40+
3341
}
3442
Section(header: Text("Notifications")) {
3543
Toggle("Enable Watch Notifications", isOn: $watchNotifications)

Infini-iOS/View Components/BLEConnectView.swift

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ struct Connect: View {
1212

1313
@EnvironmentObject var bleManager: BLEManager
1414
@State var scanOrStopScan: Bool = true
15+
@AppStorage("autoconnect") var autoconnect: Bool = false
16+
@AppStorage("autoconnectUUID") var autoconnectUUID: String = ""
1517

1618
var body: some View {
1719

@@ -36,12 +38,16 @@ struct Connect: View {
3638

3739
Spacer()
3840

39-
let autoconnect = UserDefaults.standard.object(forKey: "autoconnect") as? Bool ?? true
41+
//let autoconnect = UserDefaults.standard.object(forKey: "autoconnect") as? Bool ?? true
4042
if autoconnect {
41-
Button(action: {
42-
self.bleManager.startScanning()
43-
}) {
44-
Text("Autoconnect")
43+
//Button(action: {
44+
// self.bleManager.startScanning()
45+
//}) {
46+
if bleManager.isSwitchedOn {
47+
Text("Scanning")
48+
.onAppear {
49+
self.bleManager.startScanning()
50+
}
4551
.padding()
4652
.padding(.vertical, 7)
4753
.frame(maxWidth: .infinity, alignment: .center)
@@ -50,7 +56,6 @@ struct Connect: View {
5056
.cornerRadius(10)
5157
.padding(.horizontal, 20)
5258
}
53-
5459
} else {
5560
if scanOrStopScan {
5661
Button(action: {

Infini-iOS/View Components/BLEStatusView.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ struct DeviceView: View {
1616
VStack (spacing: 10){
1717
Text("InfiniTime Status")
1818
.font(.largeTitle)
19-
.frame(maxWidth: .infinity, alignment: .center)
20-
.padding(.bottom, 30)
21-
19+
.padding()
20+
.frame(maxWidth: .infinity, alignment: .leading)
21+
2222
HStack (spacing: 10){
2323
Text("Heart Rate: ")
2424
.font(.title)

Infini-iOS/View Components/DFUView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ struct DFU_Page: View {
2424
VStack (spacing: 10){
2525
Text("Firmware Update")
2626
.font(.largeTitle)
27-
.frame(maxWidth: .infinity, alignment: .center)
28-
.padding(.bottom, 30)
27+
.padding()
28+
.frame(maxWidth: .infinity, alignment: .leading)
2929

3030
HStack (spacing: 10){
3131
Text("Current Firmware: ")

0 commit comments

Comments
 (0)