USB-Meter / USB Meter / Model / BluetoothSerial.swift
Newer Older
293 lines | 10.599kb
Bogdan Timofte authored 2 weeks ago
1
//
2
//  bluetoothSerial.swift
3
//  USB Meter
4
//
5
//  Created by Bogdan Timofte on 17/03/2020.
6
//  Copyright © 2020 Bogdan Timofte. All rights reserved.
7
//
8
//  https://github.com/hoiberg/HM10-BluetoothSerial-iOS
9
import CoreBluetooth
10

            
11
final class BluetoothSerial : NSObject, ObservableObject {
12

            
13
    enum AdministrativeState {
14
        case down
15
        case up
16
    }
17
    enum OperationalState: Int, Comparable {
18
        case peripheralNotConnected
19
        case peripheralConnectionPending
20
        case peripheralConnected
21
        case peripheralReady
22

            
23
        static func < (lhs: OperationalState, rhs: OperationalState) -> Bool {
24
            return lhs.rawValue < rhs.rawValue
25
        }
26
    }
27

            
28
    private var administrativeState = AdministrativeState.down
29
    private var operationalState = OperationalState.peripheralNotConnected {
30
        didSet {
31
            delegate?.opertionalStateChanged(to: operationalState)
32
        }
33
    }
34

            
35
    var macAddress: MACAddress
36
    private var manager: CBCentralManager
37
    private var radio: BluetoothRadio
38
    @Published var RSSI: Int
39

            
40
    private var expectedResponseLength = 0
41
    private var wdTimer: Timer?
42

            
43
    var peripheral: CBPeripheral
44

            
45
    /// The characteristic 0xFFE1 we need to write to, of the connectedPeripheral
46
    private var writeCharacteristic: CBCharacteristic?
47

            
48
    private var buffer = Data()
49

            
50
    weak var delegate: SerialPortDelegate?
51

            
52
    init( peripheral: CBPeripheral, radio: BluetoothRadio, with macAddress: MACAddress, managedBy manager: CBCentralManager, RSSI: Int ) {
53

            
54
        self.peripheral = peripheral
55
        self.macAddress = macAddress
56
        self.radio = radio
57
        self.manager = manager
58
        self.RSSI = RSSI
59
        Timer.scheduledTimer(withTimeInterval: 3, repeats: true, block: {_ in
60
            if peripheral.state == .connected {
61
                peripheral.readRSSI()
62
            }
63
        })
64
        super.init()
65
        peripheral.delegate = self
66
    }
67

            
68
    func connect() {
69
        administrativeState = .up
70
        if operationalState < .peripheralConnected {
71
            operationalState = .peripheralConnectionPending
72
            track("Connect caled")
73
            manager.connect(peripheral, options: nil)
74
        } else {
75
            track("Peripheral allready connected: \(operationalState)")
76
        }
77
    }
78

            
79
    func disconnect() {
80
        if operationalState >= .peripheralConnected {
81
            manager.cancelPeripheralConnection(peripheral)
82
            buffer.removeAll()
83
        }
84
    }
85

            
86
    /**
87
     Send data
88

            
89
     - parameter data: Data to be sent.
90
     - parameter expectedResponseLength: Optional If message sent require a respnse the length for that response must be provideed. Incomming data will be buffered before calling delegate.didReceiveData
91
     */
92
    func write(_ data: Data, expectedResponseLength: Int = 0) {
93
        //track("\(self.expectedResponseLength)")
94
        //track(data.hexEncodedStringValue)
95
        guard operationalState == .peripheralReady else {
96
            track("Guard: \(operationalState)")
97
            return
98
        }
99
        guard self.expectedResponseLength == 0 else {
100
            track("Guard: \(self.expectedResponseLength)")
101
            return
102
        }
103

            
104
        self.expectedResponseLength = expectedResponseLength
105

            
106
//        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!")
114
        }
115
//        track("Sent!")
116
        if self.expectedResponseLength != 0 {
117
            setWDT()
118
        }
119
    }
120

            
121
    func connectionEstablished () {
122
        track("")
123
        operationalState = .peripheralConnected
124
        peripheral.discoverServices(BluetoothRadioServicesUUIDS[radio])
125
    }
126

            
127
    func connectionClosed () {
128
        track("")
129
        operationalState = .peripheralNotConnected
130
        expectedResponseLength = 0
131
        writeCharacteristic = nil
132
    }
133

            
134
    func setWDT() {
135
        wdTimer?.invalidate()
136
        wdTimer = Timer.scheduledTimer(withTimeInterval: 10, repeats: false, block: {_ in
137
            track("Response timeout. Expected: \(self.expectedResponseLength) - buffer: \(self.buffer.count)")
138
            self.expectedResponseLength = 0
139
            self.disconnect()
140
        })
141
    }
142

            
143
}
144

            
145
//  MARK:   CBPeripheralDelegate
146
extension BluetoothSerial : CBPeripheralDelegate {
147

            
148
    //  MARK:   didReadRSSI
149
    func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) {
150
        if error != nil {
151
            track( "Error: \(error!)" )
152
        }
153
        self.RSSI = RSSI.intValue
154
    }
155

            
156
    //  MARK:   didDiscoverServices
157
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
158
        track("\(String(describing: peripheral.services))")
159
        if error != nil {
160
            track( "Error: \(error!)" )
161
        }
162
        switch radio {
163
        case .BT18:
164
            // discover the 0xFFE1 characteristic for all services (though there should only be one(service))
165
            for service in peripheral.services! {
166
                switch service.uuid {
167
                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)
170
                default:
171
                    track ("Unexpected service discovered: '\(service)'")
172
                }
173
            }
174
        case .PW0316:
175
            for service in peripheral.services! {
176
                //track("\(service.uuid)")
177
                switch service.uuid {
178
                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
183
                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)
187
                default:
188
                    track ("Unexpected service discovered: '\(service)'")
189
                }
190
            }
191
            break;
192
        default:
193
            track("Radio \(radio) Not Implemented!")
194
        }
195
    }
196

            
197
    //  MARK:   didDiscoverCharacteristicsFor
198
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
199
        if error != nil {
200
            track( "Error: \(error!)" )
201
        }
202
        track("\(String(describing: service.characteristics))")
203
        switch radio {
204
        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
            }
220
        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;
236
        default:
237
            track("Radio \(radio) Not Implemented!")
238
        }
239
    }
240

            
241
    //  MARK:   didUpdateValueFor
242
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
243
//        track("")
244
        if error != nil {
245
            track( "Error: \(error!)" )
246
        }
247
        buffer.append( characteristic.value ?? Data() )
248
//        track("\n\(buffer.hexEncodedStringValue)")
249
        switch buffer.count {
250
        case let x where x < expectedResponseLength:
251
            setWDT()
252
            //track("buffering")
253
            break;
254
        case let x where x == expectedResponseLength:
255
            //track("buffer ready")
256
            wdTimer?.invalidate()
257
            expectedResponseLength = 0
258
            delegate?.didReceiveData(buffer)
259
            buffer.removeAll()
260
        case let x where x > expectedResponseLength:
261
            // MARK: De unde stim că asta a fost tot? Probabil o deconectare ar rezolva problema
262
            wdTimer?.invalidate()
263
            expectedResponseLength = 0
264
            buffer.removeAll()
265
            track("Buffer Overflow")
266
        default:
267
            track("This is not possible!")
268
        }
269
    }
270

            
271
    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
272
        if error != nil { track( "Error: \(error!)" ) }
273
    }
274

            
275
    func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral) {
276
        //track("")
277
    }
278

            
279
}
280

            
281
// MARK: SerialPortDelegate
282
protocol SerialPortDelegate: AnyObject {
283
    // MARK: State Changed
284
    func opertionalStateChanged( to newOperationalState: BluetoothSerial.OperationalState )
285
    // MARK: Data was received
286
    func didReceiveData(_ data: Data)
287
}
288

            
289
// MARK: SerialPortDelegate Optionals
290
extension SerialPortDelegate {
291
}
292

            
293