Skip to content

Commit a8ac8c0

Browse files
committed
Implemented ios client: connecting and receiving messages
1 parent fb02826 commit a8ac8c0

File tree

9 files changed

+171
-24
lines changed

9 files changed

+171
-24
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
#Sun Oct 30 20:08:02 UZT 2022
12
distributionBase=GRADLE_USER_HOME
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
24
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
4-
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists
6+
zipStoreBase=GRADLE_USER_HOME

ios/BLEClient.swift

+88-7
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,22 @@ import CoreBluetooth
1111
import Combine
1212

1313
@available(iOS 13.0, *)
14-
class BLEClient : NSObject, CBCentralManagerDelegate {
14+
class BLEClient : NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
1515

1616
private let tag = "BLEClient"
1717

18+
public var onMessageReceivedListener: ((String) -> Void)?
19+
1820
private lazy var centralManager = CBCentralManager(delegate: self, queue: nil)
1921
private var connectedPeripheral: CBPeripheral?
22+
private var characteristic: CBCharacteristic?
2023

2124
private var scanResults: AsyncStream<Result<Peripheral, BLEError>>?
2225
private var scanResultsContinuation: AsyncStream<Result<Peripheral, BLEError>>.Continuation?
2326

27+
private var connectCompletion: ((Result<Any?, BLEError>) -> Void)?
28+
29+
2430
func start() {
2531
// check the state to init bluetooth manager
2632
centralManager.state
@@ -74,8 +80,17 @@ class BLEClient : NSObject, CBCentralManagerDelegate {
7480
centralManager.stopScan()
7581
}
7682

77-
func connectToPeripheral(address: String) async {
83+
func connectToPeripheral(identifier: String, completion: @escaping (Result<Any?, BLEError>) -> Void) -> Void {
84+
log(tag: tag, message: "Trying to connect to \(identifier)")
85+
guard let uuid = UUID(uuidString: identifier), let peripheral = centralManager.retrievePeripherals(withIdentifiers: [uuid]).first else {
86+
log(tag: tag, message: "Cannot connect to \(identifier), not found.")
87+
completion(.failure(BLEError(message: "Could not find peripheral with the specified identifier \(identifier)")))
88+
return
89+
}
7890

91+
connectCompletion = completion
92+
connectedPeripheral = peripheral
93+
centralManager.connect(peripheral)
7994
}
8095

8196
func sendMessage(message: String) async {
@@ -115,11 +130,77 @@ class BLEClient : NSObject, CBCentralManagerDelegate {
115130
}
116131

117132
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
118-
// print("Connected to peer \(peripheral.identifier)")
119-
// connected = true
120-
// centralManager?.stopScan()
121-
// peripheral.delegate = self
122-
// peripheral.discoverServices([service])
133+
log(tag: tag, message: "Successfully connected to peer \(peripheral.identifier.uuidString), discovering services...")
134+
peripheral.delegate = self
135+
peripheral.discoverServices([SERVICE_ID])
136+
}
137+
138+
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
139+
let bleError = BLEError(message: "Failed to connect to peripheral \(peripheral.identifier.uuidString)", cause: error)
140+
connectedPeripheral = nil
141+
log(tag: tag, error: bleError)
142+
connectCompletion?(.failure(bleError))
143+
}
144+
145+
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
146+
if let error = error {
147+
let bleError = BLEError(message: "Error discovering services of peripheral \(peripheral.identifier.uuidString)", cause: error)
148+
log(tag: tag, error: bleError)
149+
connectCompletion?(.failure(bleError))
150+
} else {
151+
log(tag: tag, message: "Successfully discovered services, looking for our service...")
152+
153+
if let service = peripheral.services?.first(where: { $0.uuid == SERVICE_ID}) {
154+
log(tag: tag, message: "Found our service, discovering characteristics...")
155+
peripheral.discoverCharacteristics([CHARACTERISTIC_ID], for: service)
156+
} else {
157+
let bleError = BLEError(message: "Our service was not found, something went horribly wrong")
158+
log(tag: tag, error: bleError)
159+
connectCompletion?(.failure(bleError))
160+
}
161+
}
162+
}
163+
164+
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
165+
if let error = error {
166+
let bleError = BLEError(message: "Error discovering characteristics of peripheral \(peripheral.identifier.uuidString)", cause: error)
167+
log(tag: tag, error: bleError)
168+
connectCompletion?(.failure(bleError))
169+
} else {
170+
log(tag: tag, message: "Successfully discovered characteristics, looking for our characteristic...")
171+
172+
if let characteristic = service.characteristics?.first(where: { $0.uuid == CHARACTERISTIC_ID }) {
173+
log(tag: tag, message: "Found our characteristic, peripheral is ready!")
174+
self.characteristic = characteristic
175+
peripheral.setNotifyValue(true, for: characteristic)
176+
connectCompletion?(.success(nil))
177+
} else {
178+
let bleError = BLEError(message: "Our characteristic was not found, something went horribly wrong")
179+
log(tag: tag, error: bleError)
180+
connectCompletion?(.failure(bleError))
181+
}
182+
}
183+
}
184+
185+
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
186+
log(tag: tag, message: "Characteristic value updated")
187+
188+
guard let data = characteristic.value else {
189+
log(tag: tag, message: "Update was empty")
190+
return
191+
}
192+
193+
guard let dataStr = String(data: data, encoding: String.Encoding.utf8) else {
194+
log(tag: tag, message: "Could not convert received data into a string")
195+
return
196+
}
197+
198+
if !dataStr.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
199+
log(tag: tag, message: "Received data: \(dataStr)")
200+
onMessageReceivedListener?(dataStr)
201+
} else {
202+
log(tag: tag, message: "Update was empty")
203+
}
123204
}
124205

125206
private func assertBluetoothState() -> Result<Any?, BLEError> {

ios/BLEError.swift

+7-2
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,16 @@ import Foundation
1010

1111
struct BLEError : LocalizedError {
1212
let message: String
13-
let cause: Error? = nil
13+
let cause: Error?
14+
15+
init(message: String, cause: Error? = nil) {
16+
self.message = message
17+
self.cause = cause
18+
}
1419

1520
var errorDescription: String? {
1621
get {
17-
return message
22+
return "\(message). \(cause?.localizedDescription ?? "")"
1823
}
1924
}
2025

ios/BLEEventType.swift

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// BLEEventType.swift
3+
// Ble
4+
//
5+
// Created by AndrewNadraliev on 30.10.2022.
6+
// Copyright © 2022 Facebook. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
/*
12+
class MessageReceived(val payload: String): BLEEventType("ble-message-received")
13+
object StartedMessageReceive: BLEEventType("ble-started-message-receive")
14+
object ConnectingToServer: BLEEventType("ble-connecting-to-server")
15+
object ConnectedToServer: BLEEventType("ble-connected-to-server")
16+
object DisconnectingFromServer: BLEEventType("ble-disconnecting-from-server")
17+
object DisconnectedFromServer: BLEEventType("ble-disconnected-from-server")
18+
object ClientConnected: BLEEventType("ble-device-connected")
19+
object ClientDisconnected: BLEEventType("ble-device-disconnected")
20+
object SendingMessage: BLEEventType("ble-sending-message")
21+
object MessageSent: BLEEventType("ble-message-sent")
22+
*/
23+
24+
enum BLEEventType: String, CaseIterable {
25+
case MessageReceived = "ble-message-received"
26+
case StartedMessageReceive = "ble-started-message-receive"
27+
case ConnectingToServer = "ble-connecting-to-server"
28+
case ConnectedToServer = "ble-connected-to-server"
29+
case DisconnectingFromServer = "ble-disconnecting-from-server"
30+
case DisconnectedFromServer = "ble-disconnected-from-server"
31+
case ClientConnected = "ble-device-connected"
32+
case ClientDisconnected = "ble-device-disconnected"
33+
case SendingMessage = "ble-sending-message"
34+
case MessageSent = "ble-message-sent"
35+
}

ios/BLEModule.swift

+26-9
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,22 @@ import React
1111

1212
@available(iOS 13.0, *)
1313
@objc(BLEModule)
14-
class BLEModule: NSObject {
14+
class BLEModule: RCTEventEmitter {
1515

1616
private let bleClient = BLEClient()
1717

18+
@objc(supportedEvents)
19+
override func supportedEvents() -> [String]! {
20+
return BLEEventType.allCases.map { $0.rawValue }
21+
}
22+
1823
@objc(start)
1924
func start() -> Void {
2025
bleClient.start()
26+
bleClient.onMessageReceivedListener = { [weak self] value in
27+
28+
self?.sendEvent(withName: BLEEventType.MessageReceived.rawValue, body: ["payload": value])
29+
}
2130
}
2231

2332
@objc(stop)
@@ -30,35 +39,43 @@ class BLEModule: NSObject {
3039
Task.init(operation: {
3140
switch (await bleClient.scan(filterBleId: filterBleId, stopIfFound: stopIfFound)) {
3241
case .success(let identifier):
33-
if let resolve = resolve {
34-
resolve(identifier)
35-
}
42+
resolve?(identifier)
3643
case .failure(let error):
37-
if let reject = reject {
38-
reject(nil, nil, error)
39-
}
44+
reject?(nil, nil, error)
4045
}
4146
})
4247
}
4348

49+
@objc(stopScan:reject:)
4450
func stopScan(_ resolve: RCTPromiseResolveBlock?, reject: RCTPromiseRejectBlock?) {
4551
Task.init(operation: {
4652
await bleClient.stopScan()
4753
})
4854
}
4955

50-
func connectToPeripheral(_ address: String, resolve: RCTPromiseResolveBlock?, reject: RCTPromiseRejectBlock?) {
51-
56+
@objc(connectToPeripheral:resolve:reject:)
57+
func connectToPeripheral(_ address: String, resolve: RCTPromiseResolveBlock?, reject: RCTPromiseRejectBlock?) -> Void {
58+
bleClient.connectToPeripheral(identifier: address, completion: { (result: Result<Any?, BLEError>) -> Void in
59+
switch (result) {
60+
case .success(_):
61+
resolve?(nil)
62+
case .failure(let error):
63+
reject?(nil, nil, error)
64+
}
65+
})
5266
}
5367

68+
@objc(sendMessage:resolve:reject:)
5469
func sendMessage(_ message: String, resolve: RCTPromiseResolveBlock?, reject: RCTPromiseRejectBlock?) {
5570

5671
}
5772

73+
@objc(disconnect:reject:)
5874
func disconnect(_ resolve: RCTPromiseResolveBlock?, reject: RCTPromiseRejectBlock?) {
5975

6076
}
6177

78+
@objc(finish:reject:)
6279
func finish(_ resolve: RCTPromiseResolveBlock?, reject: RCTPromiseRejectBlock?) {
6380

6481
}

ios/Ble.xcodeproj/project.pbxproj

+4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
/* Begin PBXBuildFile section */
1010
6373FBEE290D6E5F00DB22BB /* BLEModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6373FBED290D6E5F00DB22BB /* BLEModule.swift */; };
1111
6373FBF5290D714400DB22BB /* BLEModuleExport.m in Sources */ = {isa = PBXBuildFile; fileRef = 6373FBF4290D714400DB22BB /* BLEModuleExport.m */; };
12+
639275CA290ED3AD00713511 /* BLEEventType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 639275C9290ED3AD00713511 /* BLEEventType.swift */; };
1213
63A673E4290E7EDB00D80A11 /* BLEClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63A673E3290E7EDB00D80A11 /* BLEClient.swift */; };
1314
63A673E6290E7F0400D80A11 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63A673E5290E7F0400D80A11 /* Constants.swift */; };
1415
63A673E8290E83EB00D80A11 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63A673E7290E83EB00D80A11 /* Utils.swift */; };
@@ -34,6 +35,7 @@
3435
6373FBED290D6E5F00DB22BB /* BLEModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEModule.swift; sourceTree = "<group>"; };
3536
6373FBF2290D6F0400DB22BB /* Ble-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Ble-Bridging-Header.h"; sourceTree = "<group>"; };
3637
6373FBF4290D714400DB22BB /* BLEModuleExport.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BLEModuleExport.m; sourceTree = "<group>"; };
38+
639275C9290ED3AD00713511 /* BLEEventType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEEventType.swift; sourceTree = "<group>"; };
3739
63A673E3290E7EDB00D80A11 /* BLEClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEClient.swift; sourceTree = "<group>"; };
3840
63A673E5290E7F0400D80A11 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
3941
63A673E7290E83EB00D80A11 /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = "<group>"; };
@@ -75,6 +77,7 @@
7577
58B511D21A9E6C8500147676 = {
7678
isa = PBXGroup;
7779
children = (
80+
639275C9290ED3AD00713511 /* BLEEventType.swift */,
7881
63A673EB290EA10F00D80A11 /* Peripheral.swift */,
7982
63A673E9290E869100D80A11 /* BLEError.swift */,
8083
63A673E7290E83EB00D80A11 /* Utils.swift */,
@@ -183,6 +186,7 @@
183186
63A673EC290EA10F00D80A11 /* Peripheral.swift in Sources */,
184187
63A673E8290E83EB00D80A11 /* Utils.swift in Sources */,
185188
6373FBEE290D6E5F00DB22BB /* BLEModule.swift in Sources */,
189+
639275CA290ED3AD00713511 /* BLEEventType.swift in Sources */,
186190
6373FBF5290D714400DB22BB /* BLEModuleExport.m in Sources */,
187191
63A673E4290E7EDB00D80A11 /* BLEClient.swift in Sources */,
188192
63A673E6290E7F0400D80A11 /* Constants.swift in Sources */,

ios/Constants.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@
99
import Foundation
1010
import CoreBluetooth
1111

12-
public let SERVICE_ID = CBUUID(nsuuid: UUID(uuidString: "6E33748B-D176-4D38-A962-8947ECEC8271")!)
13-
private let CHARACTERISTIC_ID = CBUUID(nsuuid: UUID(uuidString: "F026CBE5-A1DB-44FB-9E2F-E55FDB94B293")!)
14-
private let MANUFACTURER_ID = 0xFFFF
12+
let SERVICE_ID = CBUUID(nsuuid: UUID(uuidString: "6E33748B-D176-4D38-A962-8947ECEC8271")!)
13+
let CHARACTERISTIC_ID = CBUUID(nsuuid: UUID(uuidString: "F026CBE5-A1DB-44FB-9E2F-E55FDB94B293")!)
14+
let MANUFACTURER_ID = 0xFFFF

ios/Utils.swift

+4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ func log(tag: String, message: String, error: Error? = nil) {
1616
}
1717
}
1818

19+
func log(tag: String, error: Error) {
20+
NSLog("%s: %s", tag, error.localizedDescription)
21+
}
22+
1923
@available(iOS 13.0, *)
2024
public extension AsyncStream {
2125
/// Factory function that creates an AsyncStream and returns a tuple standing for its inputs and outputs.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@sicpa-dlab/ble-react-native",
3-
"version": "0.3.6",
3+
"version": "0.3.7",
44
"description": "React Native wrapper for Bluetooth Low Energy",
55
"main": "lib/module/index.js",
66
"module": "lib/module/index.js",

0 commit comments

Comments
 (0)