react-native-bluetooth-escpos-printer-fork 0.0.16 → 0.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -5
- package/android/.classpath +6 -0
- package/android/.gradle/6.1.1/executionHistory/executionHistory.lock +0 -0
- package/android/.gradle/6.1.1/fileChanges/last-build.bin +0 -0
- package/android/.gradle/6.1.1/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/6.1.1/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
- package/android/.gradle/checksums/checksums.lock +0 -0
- package/android/.gradle/checksums/md5-checksums.bin +0 -0
- package/android/.gradle/checksums/sha1-checksums.bin +0 -0
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/android/.project +17 -0
- package/android/.settings/org.eclipse.buildship.core.prefs +11 -0
- package/android/.settings/org.eclipse.jdt.core.prefs +4 -0
- package/android/build.gradle +0 -1
- package/android/src/main/AndroidManifest.xml +3 -1
- package/android/src/main/java/cn/jystudio/bluetooth/escpos/RNBluetoothEscposPrinterModule.java +11 -2
- package/ios/PrintImageBleWriteDelegate.m +3 -12
- package/ios/RNBluetoothEscposPrinter.m +39 -18
- package/ios/RNBluetoothManager.h +1 -0
- package/ios/RNBluetoothManager.m +397 -38
- package/package.json +1 -1
- package/package-lock.json +0 -18
package/README.md
CHANGED
|
@@ -28,6 +28,21 @@ Link the plugin to your RN project
|
|
|
28
28
|
react-native link react-native-bluetooth-escpos-printer
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
+
### Step3 (iOS only) ###
|
|
32
|
+
Add Bluetooth permissions to your `Info.plist`:
|
|
33
|
+
|
|
34
|
+
```xml
|
|
35
|
+
<key>NSBluetoothAlwaysUsageDescription</key>
|
|
36
|
+
<string>This app needs Bluetooth to discover and connect to printers</string>
|
|
37
|
+
<key>NSBluetoothPeripheralUsageDescription</key>
|
|
38
|
+
<string>This app needs Bluetooth to connect to printer peripherals</string>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Note:**
|
|
42
|
+
- For iOS 13+, `NSBluetoothAlwaysUsageDescription` is required.
|
|
43
|
+
- For iOS 12 and below, `NSBluetoothPeripheralUsageDescription` is required.
|
|
44
|
+
- It's recommended to include both for maximum compatibility.
|
|
45
|
+
|
|
31
46
|
### Manual linking (Android) ###
|
|
32
47
|
Ensure your build files match the following requirements:
|
|
33
48
|
|
|
@@ -73,8 +88,12 @@ async function, checks whether Bluetooth service is enabled.
|
|
|
73
88
|
});
|
|
74
89
|
```
|
|
75
90
|
|
|
76
|
-
* enableBluetooth ==>
|
|
77
|
-
async function, enables the bluetooth service, returns the devices information already bound and paired.
|
|
91
|
+
* enableBluetooth ==>
|
|
92
|
+
async function, enables the bluetooth service, returns the devices information already bound and paired.
|
|
93
|
+
|
|
94
|
+
**Platform differences:**
|
|
95
|
+
- **Android:** If Bluetooth is off, shows system dialog to enable it. Returns array of paired devices after enabling (or immediately if already enabled).
|
|
96
|
+
- **iOS:** Cannot programmatically enable Bluetooth (must be done in Settings by user). Returns empty array `[]` since iOS Core Bluetooth doesn't have a "paired devices" concept. Use `scanDevices()` to discover available devices.
|
|
78
97
|
|
|
79
98
|
```javascript
|
|
80
99
|
BluetoothManager.enableBluetooth().then((r)=>{
|
|
@@ -94,8 +113,12 @@ BluetoothManager.enableBluetooth().then((r)=>{
|
|
|
94
113
|
});
|
|
95
114
|
```
|
|
96
115
|
|
|
97
|
-
* disableBluetooth ==>
|
|
98
|
-
async function
|
|
116
|
+
* disableBluetooth ==>
|
|
117
|
+
async function, disables the bluetooth service.
|
|
118
|
+
|
|
119
|
+
**Platform differences:**
|
|
120
|
+
- **Android:** Programmatically disables Bluetooth and stops any active connections.
|
|
121
|
+
- **iOS:** Cannot programmatically disable Bluetooth (must be done in Settings or Control Center by user). This method resolves successfully but does nothing on iOS.
|
|
99
122
|
|
|
100
123
|
```javascript
|
|
101
124
|
BluetoothManager.disableBluetooth().then(()=>{
|
|
@@ -139,6 +162,36 @@ BluetoothManager.scanDevices()
|
|
|
139
162
|
});
|
|
140
163
|
```
|
|
141
164
|
|
|
165
|
+
**Device Information Structure:**
|
|
166
|
+
|
|
167
|
+
Each device object returned contains:
|
|
168
|
+
- `name` (string): Device name
|
|
169
|
+
- `address` (string): Device MAC address (Android) or UUID (iOS)
|
|
170
|
+
- `deviceClass` (number): Bluetooth device class
|
|
171
|
+
- `majorDeviceClass` (number): Major device class category
|
|
172
|
+
- `rssi` (number, iOS only): Signal strength indicator
|
|
173
|
+
|
|
174
|
+
**Filtering for Printers:**
|
|
175
|
+
|
|
176
|
+
To identify printer devices, check the `majorDeviceClass`:
|
|
177
|
+
```javascript
|
|
178
|
+
const MAJOR_CLASS_IMAGING = 1536; // 0x0600 - Printers, scanners, cameras
|
|
179
|
+
const DEVICE_CLASS_PRINTER = 1664; // 0x0680 - Specifically printers
|
|
180
|
+
|
|
181
|
+
// Filter found devices to show only printers
|
|
182
|
+
const printers = ss.found.filter(device =>
|
|
183
|
+
device.majorDeviceClass === MAJOR_CLASS_IMAGING ||
|
|
184
|
+
device.deviceClass === DEVICE_CLASS_PRINTER
|
|
185
|
+
);
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Platform differences:**
|
|
189
|
+
- **Android:** Device classes are provided by the OS based on the device's Bluetooth profile
|
|
190
|
+
- **iOS:** Device classes are inferred from:
|
|
191
|
+
1. Advertised service UUIDs (SPP, ESC/POS services)
|
|
192
|
+
2. Device name patterns (contains "print", "pos", "thermal", "receipt", etc.)
|
|
193
|
+
3. If no printer indicators are found, device is marked as "unknown" (class 7936)
|
|
194
|
+
|
|
142
195
|
* connect ==>
|
|
143
196
|
async function, connects the specified device, if not bound, bound dailog prompts.
|
|
144
197
|
|
|
@@ -162,8 +215,10 @@ async function, connects the specified device, if not bound, bound dailog prompt
|
|
|
162
215
|
* unpair ==>
|
|
163
216
|
async function, disconnects and unpairs the specified devices
|
|
164
217
|
|
|
218
|
+
**Note for iOS:** Due to iOS security restrictions, this method can only disconnect the device but cannot programmatically unpair it. The device will remain paired at the iOS system level. Users must unpair manually from iOS Settings > Bluetooth if needed. On Android, full unpair functionality is supported.
|
|
219
|
+
|
|
165
220
|
```javascript
|
|
166
|
-
BluetoothManager.
|
|
221
|
+
BluetoothManager.unpaire(rowData.address)
|
|
167
222
|
.then((s)=>{
|
|
168
223
|
//success here
|
|
169
224
|
},
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<classpath>
|
|
3
|
+
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11/"/>
|
|
4
|
+
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
|
|
5
|
+
<classpathentry kind="output" path="bin/default"/>
|
|
6
|
+
</classpath>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
package/android/.project
CHANGED
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
<projects>
|
|
6
6
|
</projects>
|
|
7
7
|
<buildSpec>
|
|
8
|
+
<buildCommand>
|
|
9
|
+
<name>org.eclipse.jdt.core.javabuilder</name>
|
|
10
|
+
<arguments>
|
|
11
|
+
</arguments>
|
|
12
|
+
</buildCommand>
|
|
8
13
|
<buildCommand>
|
|
9
14
|
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
|
10
15
|
<arguments>
|
|
@@ -12,6 +17,18 @@
|
|
|
12
17
|
</buildCommand>
|
|
13
18
|
</buildSpec>
|
|
14
19
|
<natures>
|
|
20
|
+
<nature>org.eclipse.jdt.core.javanature</nature>
|
|
15
21
|
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
|
16
22
|
</natures>
|
|
23
|
+
<filteredResources>
|
|
24
|
+
<filter>
|
|
25
|
+
<id>1769891269456</id>
|
|
26
|
+
<name></name>
|
|
27
|
+
<type>30</type>
|
|
28
|
+
<matcher>
|
|
29
|
+
<id>org.eclipse.core.resources.regexFilterMatcher</id>
|
|
30
|
+
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
|
|
31
|
+
</matcher>
|
|
32
|
+
</filter>
|
|
33
|
+
</filteredResources>
|
|
17
34
|
</projectDescription>
|
|
@@ -1,2 +1,13 @@
|
|
|
1
|
+
arguments=--init-script /Users/lukaszprivate/.local/share/opencode/bin/jdtls/config_mac/org.eclipse.osgi/59/0/.cp/gradle/init/init.gradle
|
|
2
|
+
auto.sync=false
|
|
3
|
+
build.scans.enabled=false
|
|
4
|
+
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
|
|
1
5
|
connection.project.dir=
|
|
2
6
|
eclipse.preferences.version=1
|
|
7
|
+
gradle.user.home=
|
|
8
|
+
java.home=/Library/Java/JavaVirtualMachines/zulu-11.jdk/Contents/Home
|
|
9
|
+
jvm.arguments=
|
|
10
|
+
offline.mode=false
|
|
11
|
+
override.workspace.settings=true
|
|
12
|
+
show.console.view=true
|
|
13
|
+
show.executions.view=true
|
package/android/build.gradle
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
|
|
2
|
-
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
2
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
3
|
+
package="cn.jystudio.bluetooth">
|
|
3
4
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
|
4
5
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
|
5
6
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
|
6
7
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
|
7
8
|
|
|
8
9
|
</manifest>
|
|
10
|
+
|
package/android/src/main/java/cn/jystudio/bluetooth/escpos/RNBluetoothEscposPrinterModule.java
CHANGED
|
@@ -449,7 +449,6 @@ public class RNBluetoothEscposPrinterModule extends ReactContextBaseJavaModule
|
|
|
449
449
|
}
|
|
450
450
|
}
|
|
451
451
|
|
|
452
|
-
|
|
453
452
|
@ReactMethod
|
|
454
453
|
public void cutOnePoint() {
|
|
455
454
|
try{
|
|
@@ -459,7 +458,17 @@ public class RNBluetoothEscposPrinterModule extends ReactContextBaseJavaModule
|
|
|
459
458
|
}catch (Exception e){
|
|
460
459
|
Log.d(TAG, e.getMessage());
|
|
461
460
|
}
|
|
462
|
-
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
@ReactMethod
|
|
464
|
+
public void printEscPosCommands(String base64encodeStr, final Promise promise) {
|
|
465
|
+
byte[] bytes = Base64.decode(base64encodeStr, Base64.DEFAULT);
|
|
466
|
+
if (sendDataByte(bytes)) {
|
|
467
|
+
promise.resolve(null);
|
|
468
|
+
} else {
|
|
469
|
+
promise.reject("FAILED_TO_PRINT_ESC_POS_COMMANDS");
|
|
470
|
+
}
|
|
471
|
+
}
|
|
463
472
|
|
|
464
473
|
private boolean sendDataByte(byte[] data) {
|
|
465
474
|
if (data==null || mService.getState() != BluetoothService.STATE_CONNECTED) {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
- (void) didWriteDataToBle: (BOOL)success
|
|
15
|
-
{
|
|
15
|
+
{
|
|
16
16
|
if(success){
|
|
17
17
|
if(_now == -1){
|
|
18
18
|
if(_pendingResolve) {_pendingResolve(nil); _pendingResolve=nil;}
|
|
@@ -43,19 +43,10 @@
|
|
|
43
43
|
{
|
|
44
44
|
@synchronized (self) {
|
|
45
45
|
NSInteger sizePerLine = (int)(_width/8);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// sizePerLine = [_toPrint length] - _now;
|
|
49
|
-
// }
|
|
50
|
-
// if(sizePerLine>0){
|
|
51
|
-
NSData *subData = [_toPrint subdataWithRange:NSMakeRange(_now, sizePerLine)];
|
|
52
|
-
NSLog(@"Write data:%@",subData);
|
|
53
|
-
[RNBluetoothManager writeValue:subData withDelegate:self];
|
|
54
|
-
//}
|
|
46
|
+
NSData *subData = [_toPrint subdataWithRange:NSMakeRange(_now, sizePerLine)];
|
|
47
|
+
[RNBluetoothManager writeValue:subData withDelegate:self];
|
|
55
48
|
_now = _now+sizePerLine;
|
|
56
49
|
[NSThread sleepForTimeInterval:0.01f];
|
|
57
|
-
|
|
58
50
|
}
|
|
59
|
-
//}while(_now<[_toPrint length]);
|
|
60
51
|
}
|
|
61
52
|
@end
|
|
@@ -97,7 +97,7 @@ RCT_EXPORT_METHOD(printerInit:(RCTPromiseResolveBlock)resolve
|
|
|
97
97
|
}else{
|
|
98
98
|
reject(@"COMMAND_NOT_SEND",@"COMMAND_NOT_SEND",nil);
|
|
99
99
|
}
|
|
100
|
-
|
|
100
|
+
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
//{GS, 'L', 0x00 , 0x00 }
|
|
@@ -111,7 +111,7 @@ RCT_EXPORT_METHOD(printerLeftSpace:(int) sp
|
|
|
111
111
|
reject(@"COMMAND_NOT_SEND",@"INVALID_VALUE",nil);
|
|
112
112
|
return;
|
|
113
113
|
}
|
|
114
|
-
|
|
114
|
+
|
|
115
115
|
if(RNBluetoothManager.isConnected){
|
|
116
116
|
NSMutableData *data = [[NSMutableData alloc] init];
|
|
117
117
|
Byte left[] = {'L'};
|
|
@@ -154,7 +154,7 @@ RCT_EXPORT_METHOD(printerUnderLine:(int)sp withResolver:(RCTPromiseResolveBlock)
|
|
|
154
154
|
}else{
|
|
155
155
|
reject(@"COMMAND_NOT_SEND",@"COMMAND_NOT_SEND",nil);
|
|
156
156
|
}
|
|
157
|
-
|
|
157
|
+
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
RCT_EXPORT_METHOD(printText:(NSString *) text withOptions:(NSDictionary *) options
|
|
@@ -195,7 +195,7 @@ RCT_EXPORT_METHOD(printText:(NSString *) text withOptions:(NSDictionary *) optio
|
|
|
195
195
|
if([@"UTF-8" isEqualToString:encoding] || [@"utf-8" isEqualToString:encoding] ){
|
|
196
196
|
nsEncoding = NSUTF8StringEncoding;
|
|
197
197
|
}
|
|
198
|
-
|
|
198
|
+
|
|
199
199
|
return nsEncoding;
|
|
200
200
|
}
|
|
201
201
|
-(void) textPrint:(NSString *) text
|
|
@@ -211,9 +211,9 @@ RCT_EXPORT_METHOD(printText:(NSString *) text withOptions:(NSDictionary *) optio
|
|
|
211
211
|
Byte *multTime[] = {intToWidth[widthTimes],intToHeight[heightTimes]};
|
|
212
212
|
NSData *bytes = [text dataUsingEncoding:[self toNSEncoding:encoding]];
|
|
213
213
|
NSLog(@"Got bytes length:%lu",[bytes length]);
|
|
214
|
-
|
|
214
|
+
|
|
215
215
|
NSMutableData *toSend = [[NSMutableData alloc] init];
|
|
216
|
-
|
|
216
|
+
|
|
217
217
|
//gsExclamationMark:{GS, '!', 0x00 };
|
|
218
218
|
[toSend appendBytes:ESC_GS length:sizeof(ESC_GS)];
|
|
219
219
|
[toSend appendBytes:SIGN length:sizeof(SIGN)];
|
|
@@ -240,7 +240,7 @@ RCT_EXPORT_METHOD(printText:(NSString *) text withOptions:(NSDictionary *) optio
|
|
|
240
240
|
[toSend appendData:bytes];
|
|
241
241
|
//LF
|
|
242
242
|
// [toSend appendBytes:&NL length:sizeof(NL)];
|
|
243
|
-
|
|
243
|
+
|
|
244
244
|
NSLog(@"Goting to write text : %@",text);
|
|
245
245
|
NSLog(@"With data: %@",toSend);
|
|
246
246
|
[RNBluetoothManager writeValue:toSend withDelegate:delegate];
|
|
@@ -326,7 +326,7 @@ RCT_EXPORT_METHOD(printColumn:(NSArray *)columnWidths
|
|
|
326
326
|
*
|
|
327
327
|
*/
|
|
328
328
|
NSMutableArray *table =[[NSMutableArray alloc] init];
|
|
329
|
-
|
|
329
|
+
|
|
330
330
|
/**splits the column text to few rows and applies the alignment **/
|
|
331
331
|
int padding = 1;
|
|
332
332
|
for(int i=0;i< [columnWidths count];i++){
|
|
@@ -338,7 +338,7 @@ RCT_EXPORT_METHOD(printColumn:(NSArray *)columnWidths
|
|
|
338
338
|
int shorter = 0;
|
|
339
339
|
int counter = 0;
|
|
340
340
|
NSMutableString *temp = [[NSMutableString alloc] init];
|
|
341
|
-
|
|
341
|
+
|
|
342
342
|
for(int c=0;c<[text length];c++){
|
|
343
343
|
unichar ch = [text characterAtIndex:c];
|
|
344
344
|
int l = (ch>= 0x4e00 && ch <= 0x9fff)?2:1;
|
|
@@ -365,7 +365,7 @@ RCT_EXPORT_METHOD(printColumn:(NSArray *)columnWidths
|
|
|
365
365
|
[splited addObject:css];
|
|
366
366
|
}
|
|
367
367
|
NSInteger align =[[columnAligns objectAtIndex:i] integerValue];
|
|
368
|
-
|
|
368
|
+
|
|
369
369
|
NSMutableArray *formated = [[NSMutableArray alloc] init];
|
|
370
370
|
for(ColumnSplitedString *s in splited){
|
|
371
371
|
NSMutableString *empty = [[NSMutableString alloc] init];
|
|
@@ -395,7 +395,7 @@ RCT_EXPORT_METHOD(printColumn:(NSArray *)columnWidths
|
|
|
395
395
|
}
|
|
396
396
|
[table addObject:formated];
|
|
397
397
|
}
|
|
398
|
-
|
|
398
|
+
|
|
399
399
|
/** try to find the max row count of the table **/
|
|
400
400
|
NSInteger maxRowCount = 0;
|
|
401
401
|
for(int i=0;i<[table count]/*column count*/;i++){
|
|
@@ -404,7 +404,7 @@ RCT_EXPORT_METHOD(printColumn:(NSArray *)columnWidths
|
|
|
404
404
|
maxRowCount = [rows count];// try to find the max row count;
|
|
405
405
|
}
|
|
406
406
|
}
|
|
407
|
-
|
|
407
|
+
|
|
408
408
|
/** loop table again to fill the rows **/
|
|
409
409
|
NSMutableArray<NSMutableString *> *rowsToPrint = [[NSMutableArray alloc] init];
|
|
410
410
|
for(int column=0;column<[table count]/*column count*/;column++){
|
|
@@ -426,7 +426,7 @@ RCT_EXPORT_METHOD(printColumn:(NSArray *)columnWidths
|
|
|
426
426
|
}
|
|
427
427
|
}
|
|
428
428
|
}
|
|
429
|
-
|
|
429
|
+
|
|
430
430
|
/** loops the rows and print **/
|
|
431
431
|
PrintColumnBleWriteDelegate *delegate = [[PrintColumnBleWriteDelegate alloc] init];
|
|
432
432
|
delegate.now = 0;
|
|
@@ -446,7 +446,7 @@ RCT_EXPORT_METHOD(printColumn:(NSArray *)columnWidths
|
|
|
446
446
|
NSLog(@"print text exception: %@",[e callStackSymbols]);
|
|
447
447
|
reject(e.name.description,e.name.description,nil);
|
|
448
448
|
}
|
|
449
|
-
|
|
449
|
+
|
|
450
450
|
}
|
|
451
451
|
}
|
|
452
452
|
|
|
@@ -495,7 +495,7 @@ RCT_EXPORT_METHOD(printPic:(NSString *) base64encodeStr withOptions:(NSDictionar
|
|
|
495
495
|
scaled = [ImageUtils imagePadLeft:paddingLeft withSource:scaled];
|
|
496
496
|
size =[scaled size];
|
|
497
497
|
}
|
|
498
|
-
|
|
498
|
+
|
|
499
499
|
unsigned char * graImage = [ImageUtils imageToGreyImage:scaled];
|
|
500
500
|
unsigned char * formatedData = [ImageUtils format_K_threshold:graImage width:size.width height:size.height];
|
|
501
501
|
NSData *dataToPrint = [ImageUtils eachLinePixToCmd:formatedData nWidth:size.width nHeight:size.height nMode:0];
|
|
@@ -528,7 +528,7 @@ RCT_EXPORT_METHOD(printQRCode:(NSString *)content
|
|
|
528
528
|
hints.encoding=NSUTF8StringEncoding;
|
|
529
529
|
hints.margin=0;
|
|
530
530
|
hints.errorCorrectionLevel = [self findCorrectionLevel:correctionLevel];
|
|
531
|
-
|
|
531
|
+
|
|
532
532
|
ZXMultiFormatWriter *writer = [ZXMultiFormatWriter writer];
|
|
533
533
|
ZXBitMatrix *result = [writer encode:content
|
|
534
534
|
format:kBarcodeFormatQRCode
|
|
@@ -553,6 +553,27 @@ RCT_EXPORT_METHOD(printQRCode:(NSString *)content
|
|
|
553
553
|
}
|
|
554
554
|
}
|
|
555
555
|
|
|
556
|
+
RCT_EXPORT_METHOD(printEscPosCommands:(NSString *)base64encodeStr
|
|
557
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
558
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
559
|
+
{
|
|
560
|
+
if(RNBluetoothManager.isConnected){
|
|
561
|
+
@try{
|
|
562
|
+
NSData *decoded = [[NSData alloc] initWithBase64EncodedString:base64encodeStr options:0];
|
|
563
|
+
|
|
564
|
+
pendingResolve = resolve;
|
|
565
|
+
pendingReject = reject;
|
|
566
|
+
[RNBluetoothManager writeValue:decoded withDelegate:self];
|
|
567
|
+
}
|
|
568
|
+
@catch(NSException *e){
|
|
569
|
+
NSLog(@"ERROR IN PRINTING ESC/POS COMMANDS: %@",[e callStackSymbols]);
|
|
570
|
+
reject(@"FAILED_TO_PRINT_ESC_POS_COMMANDS",@"FAILED_TO_PRINT_ESC_POS_COMMANDS",nil);
|
|
571
|
+
}
|
|
572
|
+
}else{
|
|
573
|
+
reject(@"BLUETOOTH_NOT_CONNECTED",@"Bluetooth device is not connected",nil);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
556
577
|
RCT_EXPORT_METHOD(printBarCode:(NSString *) str withType:(NSInteger)
|
|
557
578
|
nType width:(NSInteger) nWidth heigth:(NSInteger) nHeight
|
|
558
579
|
hriFontType:(NSInteger) nHriFontType hriFontPosition:(NSInteger) nHriFontPosition
|
|
@@ -565,7 +586,7 @@ RCT_EXPORT_METHOD(printBarCode:(NSString *) str withType:(NSInteger)
|
|
|
565
586
|
reject(@"INVALID_PARAMETER",@"INVALID_PARAMETER",nil);
|
|
566
587
|
return;
|
|
567
588
|
}
|
|
568
|
-
|
|
589
|
+
|
|
569
590
|
NSData *conentData = [str dataUsingEncoding:CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000)];
|
|
570
591
|
NSMutableData *toPrint = [[NSMutableData alloc] init];
|
|
571
592
|
int8_t * command = malloc(16);
|
|
@@ -587,7 +608,7 @@ RCT_EXPORT_METHOD(printBarCode:(NSString *) str withType:(NSInteger)
|
|
|
587
608
|
command[15] = [conentData length];
|
|
588
609
|
[toPrint appendBytes:command length:16];
|
|
589
610
|
[toPrint appendData:conentData];
|
|
590
|
-
|
|
611
|
+
|
|
591
612
|
pendingReject = reject;
|
|
592
613
|
pendingResolve = resolve;
|
|
593
614
|
[RNBluetoothManager writeValue:toPrint withDelegate:self];
|
package/ios/RNBluetoothManager.h
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
@property (nonatomic,copy) RCTPromiseResolveBlock scanResolveBlock;
|
|
20
20
|
@property (nonatomic,copy) RCTPromiseRejectBlock scanRejectBlock;
|
|
21
21
|
@property (strong,nonatomic) NSMutableDictionary <NSString *,CBPeripheral *> *foundDevices;
|
|
22
|
+
@property (strong,nonatomic) NSMutableDictionary <NSString *,NSDictionary *> *foundDevicesMetadata;
|
|
22
23
|
@property (strong,nonatomic) NSString *waitingConnect;
|
|
23
24
|
@property (nonatomic,copy) RCTPromiseResolveBlock connectResolveBlock;
|
|
24
25
|
@property (nonatomic,copy) RCTPromiseRejectBlock connectRejectBlock;
|
package/ios/RNBluetoothManager.m
CHANGED
|
@@ -21,23 +21,104 @@ static NSArray<CBUUID *> *supportServices = nil;
|
|
|
21
21
|
static NSDictionary *writeableCharactiscs = nil;
|
|
22
22
|
bool hasListeners;
|
|
23
23
|
static CBPeripheral *connected;
|
|
24
|
+
static CBCharacteristic *cachedWriteCharacteristic = nil; // Cache the writable characteristic
|
|
24
25
|
static RNBluetoothManager *instance;
|
|
25
26
|
static NSObject<WriteDataToBleDelegate> *writeDataDelegate;// delegate of write data resule;
|
|
26
27
|
static NSData *toWrite;
|
|
27
28
|
static NSTimer *timer;
|
|
28
29
|
|
|
30
|
+
-(instancetype)init {
|
|
31
|
+
if (self = [super init]) {
|
|
32
|
+
// Initialize CBCentralManager eagerly so it's ready when methods are called
|
|
33
|
+
// This triggers the centralManagerDidUpdateState callback immediately
|
|
34
|
+
[self centralManager];
|
|
35
|
+
[self initSupportServices];
|
|
36
|
+
}
|
|
37
|
+
return self;
|
|
38
|
+
}
|
|
39
|
+
|
|
29
40
|
+(Boolean)isConnected{
|
|
30
41
|
return !(connected==nil);
|
|
31
42
|
}
|
|
32
43
|
|
|
44
|
+
// Helper method to write data with automatic chunking for BLE MTU limits
|
|
45
|
+
+(BOOL)writeDataWithChunking:(NSData *)data
|
|
46
|
+
toCharacteristic:(CBCharacteristic *)characteristic
|
|
47
|
+
onPeripheral:(CBPeripheral *)peripheral {
|
|
48
|
+
CBCharacteristicWriteType writeType = (characteristic.properties & CBCharacteristicPropertyWriteWithoutResponse)
|
|
49
|
+
? CBCharacteristicWriteWithoutResponse
|
|
50
|
+
: CBCharacteristicWriteWithResponse;
|
|
51
|
+
|
|
52
|
+
// BLE has MTU limitations - need to chunk data if it's too large
|
|
53
|
+
// The minimum BLE MTU is 23 bytes, with 3 bytes overhead, leaving 20 bytes usable
|
|
54
|
+
// maximumWriteValueLengthForType often returns incorrect values for WriteWithoutResponse
|
|
55
|
+
// so we force a conservative chunk size
|
|
56
|
+
NSUInteger maxWriteLength = 20; // Safe default that works with all BLE devices
|
|
57
|
+
|
|
58
|
+
NSUInteger dataLength = [data length];
|
|
59
|
+
|
|
60
|
+
if(dataLength <= maxWriteLength) {
|
|
61
|
+
// Data fits in one write - no chunking needed
|
|
62
|
+
[peripheral writeValue:data forCharacteristic:characteristic type:writeType];
|
|
63
|
+
return YES;
|
|
64
|
+
} else {
|
|
65
|
+
// Need to chunk the data
|
|
66
|
+
NSLog(@"BLE chunking: %lu bytes -> %lu byte chunks", dataLength, maxWriteLength);
|
|
67
|
+
NSUInteger offset = 0;
|
|
68
|
+
BOOL allSuccess = YES;
|
|
69
|
+
NSUInteger chunkCount = 0;
|
|
70
|
+
|
|
71
|
+
while(offset < dataLength && allSuccess) {
|
|
72
|
+
NSUInteger chunkSize = MIN(maxWriteLength, dataLength - offset);
|
|
73
|
+
NSData *chunk = [data subdataWithRange:NSMakeRange(offset, chunkSize)];
|
|
74
|
+
|
|
75
|
+
@try {
|
|
76
|
+
[peripheral writeValue:chunk forCharacteristic:characteristic type:writeType];
|
|
77
|
+
chunkCount++;
|
|
78
|
+
|
|
79
|
+
// Small delay between chunks to avoid overwhelming the device
|
|
80
|
+
if(writeType == CBCharacteristicWriteWithoutResponse) {
|
|
81
|
+
[NSThread sleepForTimeInterval:0.01]; // 10ms delay
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
@catch(NSException *e) {
|
|
85
|
+
NSLog(@"BLE chunk write error at offset %lu: %@", offset, e);
|
|
86
|
+
allSuccess = NO;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
offset += chunkSize;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if(allSuccess) {
|
|
93
|
+
NSLog(@"BLE chunking complete: %lu chunks sent", chunkCount);
|
|
94
|
+
}
|
|
95
|
+
return allSuccess;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
33
99
|
+(void)writeValue:(NSData *) data withDelegate:(NSObject<WriteDataToBleDelegate> *) delegate
|
|
34
100
|
{
|
|
35
101
|
@try{
|
|
36
102
|
writeDataDelegate = delegate;
|
|
37
103
|
toWrite = data;
|
|
38
104
|
connected.delegate = instance;
|
|
39
|
-
|
|
40
|
-
//
|
|
105
|
+
|
|
106
|
+
// If we have a cached writable characteristic, use it directly
|
|
107
|
+
if(cachedWriteCharacteristic && connected){
|
|
108
|
+
// Use helper method to write with chunking
|
|
109
|
+
BOOL success = [RNBluetoothManager writeDataWithChunking:toWrite
|
|
110
|
+
toCharacteristic:cachedWriteCharacteristic
|
|
111
|
+
onPeripheral:connected];
|
|
112
|
+
|
|
113
|
+
if(writeDataDelegate) [writeDataDelegate didWriteDataToBle:success];
|
|
114
|
+
toWrite = nil;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// No cached characteristic, need to discover services first
|
|
119
|
+
NSLog(@"No cached characteristic, discovering services...");
|
|
120
|
+
// Discover all services (not just SPP which is for Classic BT, not BLE)
|
|
121
|
+
[connected discoverServices:nil];
|
|
41
122
|
}
|
|
42
123
|
@catch(NSException *e){
|
|
43
124
|
NSLog(@"error in writing data to %@,issue:%@",connected,e);
|
|
@@ -119,7 +200,11 @@ RCT_EXPORT_METHOD(isBluetoothEnabled:(RCTPromiseResolveBlock)resolve
|
|
|
119
200
|
RCT_EXPORT_METHOD(enableBluetooth:(RCTPromiseResolveBlock)resolve
|
|
120
201
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
121
202
|
{
|
|
122
|
-
|
|
203
|
+
// iOS doesn't allow programmatic Bluetooth enabling
|
|
204
|
+
// Bluetooth must be enabled from Settings by the user
|
|
205
|
+
// Return empty array to match Android's return type (array of paired devices)
|
|
206
|
+
// Since iOS Core Bluetooth doesn't have "paired devices" concept, we return empty array
|
|
207
|
+
resolve(@[]);
|
|
123
208
|
}
|
|
124
209
|
//disableBluetooth
|
|
125
210
|
RCT_EXPORT_METHOD(disableBluetooth:(RCTPromiseResolveBlock)resolve
|
|
@@ -141,15 +226,50 @@ RCT_EXPORT_METHOD(scanDevices:(RCTPromiseResolveBlock)resolve
|
|
|
141
226
|
}
|
|
142
227
|
self.scanResolveBlock = resolve;
|
|
143
228
|
self.scanRejectBlock = reject;
|
|
229
|
+
|
|
230
|
+
// Initialize metadata dictionary if needed
|
|
231
|
+
if(!self.foundDevicesMetadata){
|
|
232
|
+
self.foundDevicesMetadata = [[NSMutableDictionary alloc] init];
|
|
233
|
+
}
|
|
234
|
+
|
|
144
235
|
if(connected && connected.identifier){
|
|
145
|
-
|
|
146
|
-
|
|
236
|
+
NSString *name = connected.name ? connected.name : @"";
|
|
237
|
+
NSString *address = connected.identifier.UUIDString;
|
|
238
|
+
|
|
239
|
+
// Create device info with device class for connected device
|
|
240
|
+
NSMutableDictionary *deviceInfo = [[NSMutableDictionary alloc] init];
|
|
241
|
+
[deviceInfo setObject:address forKey:@"address"];
|
|
242
|
+
[deviceInfo setObject:name forKey:@"name"];
|
|
243
|
+
[deviceInfo setObject:@(0) forKey:@"rssi"]; // Unknown RSSI for already connected device
|
|
244
|
+
|
|
245
|
+
// Try to infer if it's a printer from the name
|
|
246
|
+
BOOL isPrinter = NO;
|
|
247
|
+
NSString *upperName = name.uppercaseString;
|
|
248
|
+
if ([upperName containsString:@"PRINT"] ||
|
|
249
|
+
[upperName containsString:@"POS"] ||
|
|
250
|
+
[upperName containsString:@"ESCPOS"] ||
|
|
251
|
+
[upperName containsString:@"THERMAL"] ||
|
|
252
|
+
[upperName containsString:@"RECEIPT"]) {
|
|
253
|
+
isPrinter = YES;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (isPrinter) {
|
|
257
|
+
[deviceInfo setObject:@(1536) forKey:@"majorDeviceClass"]; // Imaging
|
|
258
|
+
[deviceInfo setObject:@(1664) forKey:@"deviceClass"]; // Printer
|
|
259
|
+
} else {
|
|
260
|
+
[deviceInfo setObject:@(7936) forKey:@"majorDeviceClass"]; // Unknown
|
|
261
|
+
[deviceInfo setObject:@(7936) forKey:@"deviceClass"]; // Unknown
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
NSDictionary *peripheralStored = @{address:connected};
|
|
147
265
|
if(!self.foundDevices){
|
|
148
266
|
self.foundDevices = [[NSMutableDictionary alloc] init];
|
|
149
267
|
}
|
|
150
268
|
[self.foundDevices addEntriesFromDictionary:peripheralStored];
|
|
269
|
+
[self.foundDevicesMetadata setObject:deviceInfo forKey:address];
|
|
270
|
+
|
|
151
271
|
if(hasListeners){
|
|
152
|
-
[self sendEventWithName:EVENT_DEVICE_FOUND body:@{@"device":
|
|
272
|
+
[self sendEventWithName:EVENT_DEVICE_FOUND body:@{@"device":deviceInfo}];
|
|
153
273
|
}
|
|
154
274
|
}
|
|
155
275
|
[self.centralManager scanForPeripheralsWithServices:nil options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@NO}];
|
|
@@ -183,6 +303,13 @@ RCT_EXPORT_METHOD(connect:(NSString *)address
|
|
|
183
303
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
184
304
|
{
|
|
185
305
|
NSLog(@"Trying to connect....%@",address);
|
|
306
|
+
|
|
307
|
+
// Check if Bluetooth is ready
|
|
308
|
+
if(!self.centralManager || self.centralManager.state!=CBManagerStatePoweredOn){
|
|
309
|
+
reject(@"BLUETOOTH_NOT_READY",@"Bluetooth is not enabled or not ready. Please enable Bluetooth and try again.",nil);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
186
313
|
[self callStop];
|
|
187
314
|
if(connected){
|
|
188
315
|
NSString *connectedAddress =connected.identifier.UUIDString;
|
|
@@ -205,6 +332,13 @@ RCT_EXPORT_METHOD(connect:(NSString *)address
|
|
|
205
332
|
// Callbacks:
|
|
206
333
|
// centralManager:didConnectPeripheral:
|
|
207
334
|
// centralManager:didFailToConnectPeripheral:error:
|
|
335
|
+
|
|
336
|
+
// Set connection timeout (30 seconds)
|
|
337
|
+
if(timer && timer.isValid){
|
|
338
|
+
[timer invalidate];
|
|
339
|
+
timer = nil;
|
|
340
|
+
}
|
|
341
|
+
timer = [NSTimer scheduledTimerWithTimeInterval:30 target:self selector:@selector(handleConnectTimeout) userInfo:nil repeats:NO];
|
|
208
342
|
}else{
|
|
209
343
|
//starts the scan.
|
|
210
344
|
_waitingConnect = address;
|
|
@@ -212,32 +346,125 @@ RCT_EXPORT_METHOD(connect:(NSString *)address
|
|
|
212
346
|
[self.centralManager scanForPeripheralsWithServices:nil options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@NO}];
|
|
213
347
|
//Callbacks:
|
|
214
348
|
//centralManager:didDiscoverPeripheral:advertisementData:RSSI:
|
|
349
|
+
|
|
350
|
+
// Set scan + connection timeout (30 seconds)
|
|
351
|
+
if(timer && timer.isValid){
|
|
352
|
+
[timer invalidate];
|
|
353
|
+
timer = nil;
|
|
354
|
+
}
|
|
355
|
+
timer = [NSTimer scheduledTimerWithTimeInterval:30 target:self selector:@selector(handleConnectTimeout) userInfo:nil repeats:NO];
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
//disconnect(address)
|
|
359
|
+
RCT_EXPORT_METHOD(disconnect:(NSString *)address
|
|
360
|
+
findEventsWithResolver:(RCTPromiseResolveBlock)resolve
|
|
361
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
362
|
+
{
|
|
363
|
+
NSLog(@"Trying to disconnect device: %@",address);
|
|
364
|
+
|
|
365
|
+
// Check if Bluetooth is ready
|
|
366
|
+
if(!self.centralManager || self.centralManager.state!=CBManagerStatePoweredOn){
|
|
367
|
+
reject(@"BLUETOOTH_NOT_READY",@"Bluetooth is not enabled or not ready.",nil);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if(connected && [connected.identifier.UUIDString isEqualToString:address]){
|
|
372
|
+
[self.centralManager cancelPeripheralConnection:connected];
|
|
373
|
+
// Note: disconnection is async, but we resolve immediately like Android does
|
|
374
|
+
// The didDisconnectPeripheral callback will handle setting connected = nil
|
|
375
|
+
resolve(address);
|
|
376
|
+
} else {
|
|
377
|
+
// Device not connected or different device
|
|
378
|
+
NSLog(@"Device not connected or different device: %@",address);
|
|
379
|
+
resolve(address);
|
|
215
380
|
}
|
|
216
381
|
}
|
|
217
382
|
//unpaire(address)
|
|
383
|
+
RCT_EXPORT_METHOD(unpaire:(NSString *)address
|
|
384
|
+
findEventsWithResolver:(RCTPromiseResolveBlock)resolve
|
|
385
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
386
|
+
{
|
|
387
|
+
NSLog(@"Trying to unpair/disconnect device: %@",address);
|
|
388
|
+
if(connected && [connected.identifier.UUIDString isEqualToString:address]){
|
|
389
|
+
[self.centralManager cancelPeripheralConnection:connected];
|
|
390
|
+
// Note: Core Bluetooth on iOS doesn't support programmatic unpairing
|
|
391
|
+
// The connection will be cancelled, but the device remains paired at OS level
|
|
392
|
+
// Users must unpair manually from iOS Settings if needed
|
|
393
|
+
connected = nil;
|
|
394
|
+
resolve(address);
|
|
395
|
+
} else {
|
|
396
|
+
// Device not connected, just resolve
|
|
397
|
+
NSLog(@"Device not connected, nothing to unpair: %@",address);
|
|
398
|
+
resolve(address);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
218
402
|
|
|
403
|
+
-(void)handleConnectTimeout{
|
|
404
|
+
NSLog(@"Connection timeout - device not found or unable to connect");
|
|
405
|
+
if(self.centralManager.isScanning){
|
|
406
|
+
[self.centralManager stopScan];
|
|
407
|
+
}
|
|
408
|
+
if(self.connectRejectBlock){
|
|
409
|
+
RCTPromiseRejectBlock rjBlock = self.connectRejectBlock;
|
|
410
|
+
rjBlock(@"CONNECTION_TIMEOUT",@"Could not connect to device - timeout after 30 seconds",nil);
|
|
411
|
+
self.connectRejectBlock = nil;
|
|
412
|
+
self.connectResolveBlock = nil;
|
|
413
|
+
}
|
|
414
|
+
_waitingConnect = nil;
|
|
415
|
+
if(timer && timer.isValid){
|
|
416
|
+
[timer invalidate];
|
|
417
|
+
timer = nil;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
219
420
|
|
|
220
421
|
-(void)callStop{
|
|
221
422
|
if(self.centralManager.isScanning){
|
|
222
423
|
[self.centralManager stopScan];
|
|
223
424
|
NSMutableArray *devices = [[NSMutableArray alloc] init];
|
|
224
425
|
for(NSString *key in self.foundDevices){
|
|
225
|
-
NSLog(@"insert found
|
|
226
|
-
|
|
227
|
-
if
|
|
228
|
-
|
|
426
|
+
NSLog(@"insert found devices:%@ =>%@",key,[self.foundDevices objectForKey:key]);
|
|
427
|
+
|
|
428
|
+
// Get stored metadata if available, otherwise create basic info
|
|
429
|
+
NSDictionary *deviceInfo = [self.foundDevicesMetadata objectForKey:key];
|
|
430
|
+
if (deviceInfo) {
|
|
431
|
+
[devices addObject:deviceInfo];
|
|
432
|
+
} else {
|
|
433
|
+
// Fallback if metadata wasn't stored (shouldn't happen with new code)
|
|
434
|
+
NSString *name = [self.foundDevices objectForKey:key].name;
|
|
435
|
+
if(!name){
|
|
436
|
+
name = @"";
|
|
437
|
+
}
|
|
438
|
+
[devices addObject:@{
|
|
439
|
+
@"address":key,
|
|
440
|
+
@"name":name,
|
|
441
|
+
@"deviceClass":@(7936), // Unknown device class
|
|
442
|
+
@"majorDeviceClass":@(7936) // Unknown major device class
|
|
443
|
+
}];
|
|
229
444
|
}
|
|
230
|
-
[devices addObject:@{@"address":key,@"name":name}];
|
|
231
445
|
}
|
|
446
|
+
|
|
447
|
+
// Create result object matching Android format: {"paired": [...], "found": [...]}
|
|
448
|
+
// Empty paired devices array (iOS doesn't have "paired" concept like Android)
|
|
449
|
+
NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
|
|
450
|
+
[result setObject:@[] forKey:@"paired"];
|
|
451
|
+
[result setObject:devices forKey:@"found"];
|
|
452
|
+
|
|
453
|
+
// Convert the entire result to a JSON string (matching Android's behavior)
|
|
232
454
|
NSError *error = nil;
|
|
233
|
-
NSData*
|
|
234
|
-
NSString *
|
|
455
|
+
NSData* resultJsonData = [NSJSONSerialization dataWithJSONObject:result options:0 error:&error];
|
|
456
|
+
NSString *resultJsonStr = [[NSString alloc] initWithData:resultJsonData encoding:NSUTF8StringEncoding];
|
|
457
|
+
|
|
235
458
|
if(hasListeners){
|
|
236
|
-
|
|
459
|
+
// For events, send separate paired and found strings
|
|
460
|
+
NSData* foundJsonData = [NSJSONSerialization dataWithJSONObject:devices options:0 error:&error];
|
|
461
|
+
NSString *foundJsonStr = [[NSString alloc] initWithData:foundJsonData encoding:NSUTF8StringEncoding];
|
|
462
|
+
[self sendEventWithName:EVENT_DEVICE_DISCOVER_DONE body:@{@"found":foundJsonStr,@"paired":@"[]"}];
|
|
237
463
|
}
|
|
238
464
|
if(self.scanResolveBlock){
|
|
239
465
|
RCTPromiseResolveBlock rsBlock = self.scanResolveBlock;
|
|
240
|
-
|
|
466
|
+
// Return single JSON string: "{\"paired\":[],\"found\":[...]}"
|
|
467
|
+
rsBlock(resultJsonStr);
|
|
241
468
|
self.scanResolveBlock = nil;
|
|
242
469
|
}
|
|
243
470
|
}
|
|
@@ -251,9 +478,11 @@ RCT_EXPORT_METHOD(connect:(NSString *)address
|
|
|
251
478
|
- (void) initSupportServices
|
|
252
479
|
{
|
|
253
480
|
if(!supportServices){
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
481
|
+
// Use standard SPP (Serial Port Profile) UUID - same as Android
|
|
482
|
+
CBUUID *spp = [CBUUID UUIDWithString: @"00001101-0000-1000-8000-00805F9B34FB"];
|
|
483
|
+
supportServices = [NSArray arrayWithObject:spp];
|
|
484
|
+
// SPP uses the same UUID for service and characteristic
|
|
485
|
+
writeableCharactiscs = @{spp:@"00001101-0000-1000-8000-00805F9B34FB"};
|
|
257
486
|
}
|
|
258
487
|
}
|
|
259
488
|
|
|
@@ -284,19 +513,108 @@ RCT_EXPORT_METHOD(connect:(NSString *)address
|
|
|
284
513
|
* CBCentralManagerDelegate
|
|
285
514
|
**/
|
|
286
515
|
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
|
|
287
|
-
|
|
516
|
+
NSString *stateString;
|
|
517
|
+
switch (central.state) {
|
|
518
|
+
case CBManagerStateUnknown:
|
|
519
|
+
stateString = @"Unknown";
|
|
520
|
+
break;
|
|
521
|
+
case CBManagerStateResetting:
|
|
522
|
+
stateString = @"Resetting";
|
|
523
|
+
break;
|
|
524
|
+
case CBManagerStateUnsupported:
|
|
525
|
+
stateString = @"Unsupported";
|
|
526
|
+
break;
|
|
527
|
+
case CBManagerStateUnauthorized:
|
|
528
|
+
stateString = @"Unauthorized";
|
|
529
|
+
break;
|
|
530
|
+
case CBManagerStatePoweredOff:
|
|
531
|
+
stateString = @"PoweredOff";
|
|
532
|
+
break;
|
|
533
|
+
case CBManagerStatePoweredOn:
|
|
534
|
+
stateString = @"PoweredOn";
|
|
535
|
+
break;
|
|
536
|
+
default:
|
|
537
|
+
stateString = @"Unknown";
|
|
538
|
+
break;
|
|
539
|
+
}
|
|
540
|
+
NSLog(@"Bluetooth State Changed: %@ (%ld)", stateString, (long)central.state);
|
|
288
541
|
}
|
|
289
542
|
|
|
290
543
|
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI{
|
|
291
544
|
NSLog(@"did discover peripheral: %@",peripheral);
|
|
292
|
-
|
|
545
|
+
|
|
546
|
+
// Extract device information with enhanced metadata
|
|
547
|
+
NSMutableDictionary *deviceInfo = [[NSMutableDictionary alloc] init];
|
|
548
|
+
[deviceInfo setObject:peripheral.identifier.UUIDString forKey:@"address"];
|
|
549
|
+
[deviceInfo setObject:(peripheral.name ? peripheral.name : @"") forKey:@"name"];
|
|
550
|
+
|
|
551
|
+
// Add RSSI for signal strength
|
|
552
|
+
[deviceInfo setObject:RSSI forKey:@"rssi"];
|
|
553
|
+
|
|
554
|
+
// Try to infer device class from advertising data
|
|
555
|
+
// iOS BLE doesn't expose Classic Bluetooth device class, but we can make educated guesses
|
|
556
|
+
NSInteger deviceClass = 0;
|
|
557
|
+
NSInteger majorDeviceClass = 0;
|
|
558
|
+
|
|
559
|
+
// Check if device advertises printer-related services
|
|
560
|
+
NSArray *serviceUUIDs = advertisementData[CBAdvertisementDataServiceUUIDsKey];
|
|
561
|
+
BOOL isPrinter = NO;
|
|
562
|
+
|
|
563
|
+
if (serviceUUIDs && serviceUUIDs.count > 0) {
|
|
564
|
+
for (CBUUID *uuid in serviceUUIDs) {
|
|
565
|
+
NSString *uuidString = uuid.UUIDString.uppercaseString;
|
|
566
|
+
// Serial Port Profile (SPP) UUID - common for printers
|
|
567
|
+
// Also check for other printer-related service UUIDs
|
|
568
|
+
if ([uuidString isEqualToString:@"00001101-0000-1000-8000-00805F9B34FB"] ||
|
|
569
|
+
[uuidString isEqualToString:@"49535343-FE7D-4AE5-8FA9-9FAFD205E455"] ||
|
|
570
|
+
[uuidString containsString:@"18F0"]) {
|
|
571
|
+
isPrinter = YES;
|
|
572
|
+
break;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Check device name for printer keywords
|
|
578
|
+
NSString *name = peripheral.name ? peripheral.name.uppercaseString : @"";
|
|
579
|
+
if ([name containsString:@"PRINT"] ||
|
|
580
|
+
[name containsString:@"POS"] ||
|
|
581
|
+
[name containsString:@"ESCPOS"] ||
|
|
582
|
+
[name containsString:@"THERMAL"] ||
|
|
583
|
+
[name containsString:@"RECEIPT"]) {
|
|
584
|
+
isPrinter = YES;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (isPrinter) {
|
|
588
|
+
// Android Bluetooth Class values for printers:
|
|
589
|
+
// Major Device Class: 0x0600 (Imaging - printers, scanners, cameras, etc.)
|
|
590
|
+
// Device Class: 0x000680 (Imaging/Printer)
|
|
591
|
+
majorDeviceClass = 1536; // 0x0600 in decimal
|
|
592
|
+
deviceClass = 1664; // 0x0680 in decimal
|
|
593
|
+
} else {
|
|
594
|
+
// Unknown/Unclassified device
|
|
595
|
+
// Major Device Class: 0x1F00 (Uncategorized)
|
|
596
|
+
majorDeviceClass = 7936; // 0x1F00 in decimal
|
|
597
|
+
deviceClass = 7936;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
[deviceInfo setObject:@(deviceClass) forKey:@"deviceClass"];
|
|
601
|
+
[deviceInfo setObject:@(majorDeviceClass) forKey:@"majorDeviceClass"];
|
|
602
|
+
|
|
603
|
+
// Store the peripheral object for connection
|
|
293
604
|
NSDictionary *peripheralStored = @{peripheral.identifier.UUIDString:peripheral};
|
|
294
605
|
if(!self.foundDevices){
|
|
295
606
|
self.foundDevices = [[NSMutableDictionary alloc] init];
|
|
296
607
|
}
|
|
297
608
|
[self.foundDevices addEntriesFromDictionary:peripheralStored];
|
|
609
|
+
|
|
610
|
+
// Store device metadata for later retrieval
|
|
611
|
+
if(!self.foundDevicesMetadata){
|
|
612
|
+
self.foundDevicesMetadata = [[NSMutableDictionary alloc] init];
|
|
613
|
+
}
|
|
614
|
+
[self.foundDevicesMetadata setObject:deviceInfo forKey:peripheral.identifier.UUIDString];
|
|
615
|
+
|
|
298
616
|
if(hasListeners){
|
|
299
|
-
[self sendEventWithName:EVENT_DEVICE_FOUND body:@{@"device":
|
|
617
|
+
[self sendEventWithName:EVENT_DEVICE_FOUND body:@{@"device":deviceInfo}];
|
|
300
618
|
}
|
|
301
619
|
if(_waitingConnect && [_waitingConnect isEqualToString: peripheral.identifier.UUIDString]){
|
|
302
620
|
[self.centralManager connectPeripheral:peripheral options:nil];
|
|
@@ -308,10 +626,24 @@ RCT_EXPORT_METHOD(connect:(NSString *)address
|
|
|
308
626
|
NSLog(@"did connected: %@",peripheral);
|
|
309
627
|
connected = peripheral;
|
|
310
628
|
NSString *pId = peripheral.identifier.UUIDString;
|
|
629
|
+
|
|
630
|
+
// Set peripheral delegate to receive service/characteristic callbacks
|
|
631
|
+
peripheral.delegate = self;
|
|
632
|
+
|
|
311
633
|
if(_waitingConnect && [_waitingConnect isEqualToString: pId] && self.connectResolveBlock){
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
634
|
+
// Cancel connection timeout timer if it's running
|
|
635
|
+
if(timer && timer.isValid){
|
|
636
|
+
[timer invalidate];
|
|
637
|
+
timer = nil;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Pre-discover services to prepare for future write operations
|
|
641
|
+
// This matches Android's behavior where the connection is fully ready after connect
|
|
642
|
+
// Pass nil to discover ALL services (not just SPP which is for Classic BT, not BLE)
|
|
643
|
+
NSLog(@"Pre-discovering all services for faster write operations...");
|
|
644
|
+
[peripheral discoverServices:nil];
|
|
645
|
+
|
|
646
|
+
// Resolve promise immediately (iOS BLE connects fast, service discovery happens async)
|
|
315
647
|
self.connectResolveBlock(nil);
|
|
316
648
|
_waitingConnect = nil;
|
|
317
649
|
self.connectRejectBlock = nil;
|
|
@@ -324,6 +656,9 @@ RCT_EXPORT_METHOD(connect:(NSString *)address
|
|
|
324
656
|
}
|
|
325
657
|
|
|
326
658
|
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error{
|
|
659
|
+
// Clear cached characteristic when disconnecting
|
|
660
|
+
cachedWriteCharacteristic = nil;
|
|
661
|
+
|
|
327
662
|
if(!connected && _waitingConnect && [_waitingConnect isEqualToString:peripheral.identifier.UUIDString]){
|
|
328
663
|
if(self.connectRejectBlock){
|
|
329
664
|
RCTPromiseRejectBlock rjBlock = self.connectRejectBlock;
|
|
@@ -345,6 +680,9 @@ RCT_EXPORT_METHOD(connect:(NSString *)address
|
|
|
345
680
|
}
|
|
346
681
|
|
|
347
682
|
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error{
|
|
683
|
+
// Clear cached characteristic when connection fails
|
|
684
|
+
cachedWriteCharacteristic = nil;
|
|
685
|
+
|
|
348
686
|
if(self.connectRejectBlock){
|
|
349
687
|
RCTPromiseRejectBlock rjBlock = self.connectRejectBlock;
|
|
350
688
|
rjBlock(@"",@"",error);
|
|
@@ -412,38 +750,59 @@ RCT_EXPORT_METHOD(connect:(NSString *)address
|
|
|
412
750
|
*/
|
|
413
751
|
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(nullable NSError *)error{
|
|
414
752
|
if(toWrite && connected
|
|
415
|
-
&& [connected.identifier.UUIDString isEqualToString:peripheral.identifier.UUIDString]
|
|
416
|
-
&& [service.UUID.UUIDString isEqualToString:supportServices[0].UUIDString]){
|
|
753
|
+
&& [connected.identifier.UUIDString isEqualToString:peripheral.identifier.UUIDString]){
|
|
417
754
|
if(error){
|
|
418
|
-
NSLog(@"
|
|
755
|
+
NSLog(@"Discover characteristics error:%@",error);
|
|
419
756
|
if(writeDataDelegate)
|
|
420
757
|
{
|
|
421
758
|
[writeDataDelegate didWriteDataToBle:false];
|
|
422
759
|
return;
|
|
423
760
|
}
|
|
424
761
|
}
|
|
762
|
+
|
|
763
|
+
// Try to write to any service that has writable characteristics
|
|
764
|
+
// Don't filter by supportServices - many BLE printers use custom UUIDs
|
|
765
|
+
NSLog(@"Checking service %@ for writable characteristics...", service.UUID.UUIDString);
|
|
766
|
+
|
|
767
|
+
// Auto-detect writable characteristic by checking properties
|
|
425
768
|
for(CBCharacteristic *cc in service.characteristics){
|
|
426
|
-
NSLog(@"
|
|
427
|
-
|
|
769
|
+
NSLog(@"Characteristic found: %@ in service: %@" ,cc,service.UUID.UUIDString);
|
|
770
|
+
|
|
771
|
+
// Check if characteristic supports write operations
|
|
772
|
+
BOOL canWrite = (cc.properties & CBCharacteristicPropertyWrite) ||
|
|
773
|
+
(cc.properties & CBCharacteristicPropertyWriteWithoutResponse);
|
|
774
|
+
|
|
775
|
+
if(canWrite){
|
|
428
776
|
@try{
|
|
429
|
-
[
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
777
|
+
NSLog(@"Writing %lu bytes to characteristic %@ in service %@",[toWrite length], cc.UUID.UUIDString, service.UUID.UUIDString);
|
|
778
|
+
|
|
779
|
+
// Use helper method to write with chunking
|
|
780
|
+
BOOL success = [RNBluetoothManager writeDataWithChunking:toWrite
|
|
781
|
+
toCharacteristic:cc
|
|
782
|
+
onPeripheral:connected];
|
|
783
|
+
|
|
784
|
+
// Cache this characteristic for future writes
|
|
785
|
+
cachedWriteCharacteristic = cc;
|
|
786
|
+
NSLog(@"Cached characteristic %@ for future writes", cc.UUID.UUIDString);
|
|
787
|
+
|
|
788
|
+
if(writeDataDelegate) [writeDataDelegate didWriteDataToBle:success];
|
|
789
|
+
toWrite = nil; // Clear the write buffer
|
|
790
|
+
return; // Success, exit after first successful write
|
|
434
791
|
}
|
|
435
792
|
@catch(NSException *e){
|
|
436
|
-
NSLog(@"
|
|
437
|
-
|
|
793
|
+
NSLog(@"ERROR IN WRITE VALUE: %@",e);
|
|
794
|
+
[writeDataDelegate didWriteDataToBle:false];
|
|
438
795
|
}
|
|
439
796
|
}
|
|
440
797
|
}
|
|
441
798
|
|
|
442
|
-
|
|
799
|
+
// If we get here, no writable characteristic was found in this service
|
|
800
|
+
NSLog(@"No writable characteristic found in service: %@", service.UUID.UUIDString);
|
|
801
|
+
// Don't call didWriteDataToBle:false here - there might be other services to check
|
|
443
802
|
}
|
|
444
803
|
|
|
445
804
|
if(error){
|
|
446
|
-
NSLog(@"
|
|
805
|
+
NSLog(@"Discover characteristics error:%@",error);
|
|
447
806
|
return;
|
|
448
807
|
}
|
|
449
808
|
|
package/package.json
CHANGED
package/package-lock.json
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "react-native-bluetooth-escpos-printer-fork",
|
|
3
|
-
"version": "0.0.6",
|
|
4
|
-
"lockfileVersion": 1,
|
|
5
|
-
"requires": true,
|
|
6
|
-
"dependencies": {
|
|
7
|
-
"jetifier": {
|
|
8
|
-
"version": "1.6.6",
|
|
9
|
-
"resolved": "https://registry.npmjs.org/jetifier/-/jetifier-1.6.6.tgz",
|
|
10
|
-
"integrity": "sha512-JNAkmPeB/GS2tCRqUzRPsTOHpGDah7xP18vGJfIjZC+W2sxEHbxgJxetIjIqhjQ3yYbYNEELkM/spKLtwoOSUQ=="
|
|
11
|
-
},
|
|
12
|
-
"jetify": {
|
|
13
|
-
"version": "1.0.2",
|
|
14
|
-
"resolved": "https://registry.npmjs.org/jetify/-/jetify-1.0.2.tgz",
|
|
15
|
-
"integrity": "sha512-LmeaeeM5l37oZRs6O0O0jMafEw7ra1++YJN2tnbCPnfjulnXryMlQKRib/4e64fJ2rzOWomyDsSFjD+7ZSXyKg=="
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
}
|