Showing 2 changed files with 87 additions and 51 deletions
+25 -0
USB Meter/Model/BluetoothRadio.swift
@@ -30,6 +30,31 @@ var BluetoothRadioServicesUUIDS: [BluetoothRadio:[CBUUID]] = [
30 30
     .PW0316 : [CBUUID(string: "FFE0"), CBUUID(string: "FFE5")]
31 31
 ]
32 32
 
33
+/**
34
+ Dictionary containing preferred notification characteristics used in our communication.
35
+
36
+ Notes:
37
+ - UM-family radios are documented around an HM-10 style `FFE0` / `FFE1` path
38
+ - DX-BT18 can additionally expose `FFE2` as a write-only characteristic
39
+ - TC66C PW0316 uses `FFE4` for notifications
40
+ */
41
+var BluetoothRadioNotifyUUIDs: [BluetoothRadio:[CBUUID]] = [
42
+    .BT18 : [CBUUID(string: "FFE1")],
43
+    .PW0316 : [CBUUID(string: "FFE4")]
44
+]
45
+
46
+/**
47
+ Dictionary containing preferred write characteristics used in our communication.
48
+
49
+ Preference order matters:
50
+ - for BT18 radios we prefer `FFE2` when available and fall back to `FFE1`
51
+ - for PW0316 radios we write to `FFE9`
52
+ */
53
+var BluetoothRadioWriteUUIDs: [BluetoothRadio:[CBUUID]] = [
54
+    .BT18 : [CBUUID(string: "FFE2"), CBUUID(string: "FFE1")],
55
+    .PW0316 : [CBUUID(string: "FFE9")]
56
+]
57
+
33 58
 /**
34 59
  Returns an array containing all service UUIDs used by radios
35 60
  */
+62 -51
USB Meter/Model/BluetoothSerial.swift
@@ -42,8 +42,10 @@ final class BluetoothSerial : NSObject, ObservableObject {
42 42
         
43 43
     var peripheral: CBPeripheral
44 44
     
45
-    /// The characteristic 0xFFE1 we need to write to, of the connectedPeripheral
45
+    /// The characteristic used for writes on the connected peripheral.
46 46
     private var writeCharacteristic: CBCharacteristic?
47
+    /// The characteristic used for notifications on the connected peripheral.
48
+    private var notifyCharacteristic: CBCharacteristic?
47 49
     
48 50
     private var buffer = Data()
49 51
     
@@ -104,14 +106,14 @@ final class BluetoothSerial : NSObject, ObservableObject {
104 106
         self.expectedResponseLength = expectedResponseLength
105 107
         
106 108
 //        track("Sending...")
107
-        switch radio {
108
-        case .BT18 :
109
-            peripheral.writeValue(data, for: writeCharacteristic!, type: .withoutResponse)
110
-        case .PW0316 :
111
-            peripheral.writeValue(data, for: writeCharacteristic!, type: .withResponse)
112
-        default:
113
-            track("Radio \(radio) Not Implemented!")
109
+        guard let writeCharacteristic else {
110
+            track("Missing write characteristic for \(radio)")
111
+            self.expectedResponseLength = 0
112
+            return
114 113
         }
114
+
115
+        let writeType: CBCharacteristicWriteType = writeCharacteristic.properties.contains(.writeWithoutResponse) ? .withoutResponse : .withResponse
116
+        peripheral.writeValue(data, for: writeCharacteristic, type: writeType)
115 117
 //        track("Sent!")
116 118
         if self.expectedResponseLength != 0 {
117 119
             setWDT()
@@ -129,6 +131,7 @@ final class BluetoothSerial : NSObject, ObservableObject {
129 131
         operationalState = .peripheralNotConnected
130 132
         expectedResponseLength = 0
131 133
         writeCharacteristic = nil
134
+        notifyCharacteristic = nil
132 135
     }
133 136
 
134 137
     func setWDT() {
@@ -140,6 +143,52 @@ final class BluetoothSerial : NSObject, ObservableObject {
140 143
         })
141 144
     }
142 145
 
146
+    private func refreshOperationalStateIfReady() {
147
+        guard notifyCharacteristic != nil, writeCharacteristic != nil else {
148
+            return
149
+        }
150
+        operationalState = .peripheralReady
151
+    }
152
+
153
+    private func updateBT18Characteristics(for service: CBService) {
154
+        for characteristic in service.characteristics ?? [] {
155
+            switch characteristic.uuid {
156
+            case CBUUID(string: "FFE1"):
157
+                if characteristic.properties.contains(.notify) || characteristic.properties.contains(.indicate) {
158
+                    peripheral.setNotifyValue(true, for: characteristic)
159
+                    notifyCharacteristic = characteristic
160
+                }
161
+                if writeCharacteristic == nil &&
162
+                    (characteristic.properties.contains(.write) || characteristic.properties.contains(.writeWithoutResponse)) {
163
+                    writeCharacteristic = characteristic
164
+                }
165
+            case CBUUID(string: "FFE2"):
166
+                if characteristic.properties.contains(.write) || characteristic.properties.contains(.writeWithoutResponse) {
167
+                    // DX-BT18 documents FFE2 as the preferred write-only endpoint when present.
168
+                    writeCharacteristic = characteristic
169
+                }
170
+            default:
171
+                track ("Unexpected characteristic discovered: '\(characteristic)'")
172
+            }
173
+        }
174
+        refreshOperationalStateIfReady()
175
+    }
176
+
177
+    private func updatePW0316Characteristics(for service: CBService) {
178
+        for characteristic in service.characteristics ?? [] {
179
+            switch characteristic.uuid {
180
+            case CBUUID(string: "FFE9"): // TX from BLE side into UART
181
+                writeCharacteristic = characteristic
182
+            case CBUUID(string: "FFE4"): // RX notifications from UART side into BLE
183
+                peripheral.setNotifyValue(true, for: characteristic)
184
+                notifyCharacteristic = characteristic
185
+            default:
186
+                track ("Unexpected characteristic discovered: '\(characteristic)'")
187
+            }
188
+        }
189
+        refreshOperationalStateIfReady()
190
+    }
191
+
143 192
 }
144 193
 
145 194
 //  MARK:   CBPeripheralDelegate
@@ -161,34 +210,25 @@ extension BluetoothSerial : CBPeripheralDelegate {
161 210
         }
162 211
         switch radio {
163 212
         case .BT18:
164
-            // discover the 0xFFE1 characteristic for all services (though there should only be one(service))
165 213
             for service in peripheral.services! {
166 214
                 switch service.uuid {
167 215
                 case CBUUID(string: "FFE0"):
168
-                    // check whether the characteristic we're looking for (0xFFE1) is present - just to be sure
169
-                    peripheral.discoverCharacteristics([CBUUID(string: "FFE1")], for: service)
216
+                    peripheral.discoverCharacteristics(Array(Set((BluetoothRadioNotifyUUIDs[radio] ?? []) + (BluetoothRadioWriteUUIDs[radio] ?? []))), for: service)
170 217
                 default:
171 218
                     track ("Unexpected service discovered: '\(service)'")
172 219
                 }
173 220
             }
174 221
         case .PW0316:
175 222
             for service in peripheral.services! {
176
-                //track("\(service.uuid)")
177 223
                 switch service.uuid {
178 224
                 case CBUUID(string: "FFE0"):
179
-                    //track("\(service.uuid)")
180
-                    // check whether the characteristic we're looking for (0xFFE4) is present - just to be sure
181
-                    peripheral.discoverCharacteristics([CBUUID(string: "FFE4")], for: service)
182
-                    break
225
+                    peripheral.discoverCharacteristics(BluetoothRadioNotifyUUIDs[radio], for: service)
183 226
                 case CBUUID(string: "FFE5"):
184
-                    //track("\(service.uuid)")
185
-                    // check whether the characteristic we're looking for (0xFFE9) is present - just to be sure
186
-                    peripheral.discoverCharacteristics([CBUUID(string: "FFE9")], for: service)
227
+                    peripheral.discoverCharacteristics(BluetoothRadioWriteUUIDs[radio], for: service)
187 228
                 default:
188 229
                     track ("Unexpected service discovered: '\(service)'")
189 230
                 }
190 231
             }
191
-            break;
192 232
         default:
193 233
             track("Radio \(radio) Not Implemented!")
194 234
         }
@@ -202,37 +242,9 @@ extension BluetoothSerial : CBPeripheralDelegate {
202 242
         track("\(String(describing: service.characteristics))")
203 243
         switch radio {
204 244
         case .BT18:
205
-            // check whether the characteristic we're looking for (0xFFE1) is present - just to be sure
206
-            for characteristic in service.characteristics! {
207
-                //track(characteristic.debugDescription)
208
-                switch characteristic.uuid {
209
-                case CBUUID(string: "FFE1"):
210
-                    // subscribe to this value (so we'll get notified when there is serial data for us..)
211
-                    peripheral.setNotifyValue(true, for: characteristic)
212
-                    // keep a reference to this characteristic so we can write to it
213
-                    writeCharacteristic = characteristic
214
-                    // Change State
215
-                    operationalState = .peripheralReady
216
-                default:
217
-                    track ("Unexpected characteristic discovered: '\(characteristic)'")
218
-                }
219
-            }
245
+            updateBT18Characteristics(for: service)
220 246
         case .PW0316:
221
-            for characteristic in service.characteristics! {
222
-                switch characteristic.uuid {
223
-                case CBUUID(string: "FFE9"): //TX
224
-                    //track("characteristic FFE9: \(characteristic.properties & CBCharacteristicProperties.write)")
225
-                    writeCharacteristic = characteristic
226
-                    operationalState = .peripheralReady
227
-                case CBUUID(string: "FFE4"): //RX
228
-                    peripheral.setNotifyValue(true, for: characteristic)
229
-                    //track("characteristic FFE4: \(characteristic.properties)")
230
-                default:
231
-                    track ("Unexpected characteristic discovered: '\(characteristic)'")
232
-                }
233
-                
234
-            }
235
-            break;
247
+            updatePW0316Characteristics(for: service)
236 248
         default:
237 249
             track("Radio \(radio) Not Implemented!")
238 250
         }
@@ -290,4 +302,3 @@ protocol SerialPortDelegate: AnyObject {
290 302
 extension SerialPortDelegate {
291 303
 }
292 304
 
293
-