@wisdomgarden/capacitor-plugin-beacon 0.0.1 → 0.0.3
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/CHANGE-LOG.md +13 -0
- package/android/src/main/java/com/wisdomgarden/mobile/beacon/Beacon.java +100 -58
- package/android/src/main/java/com/wisdomgarden/mobile/beacon/BeaconUtils.java +110 -13
- package/ios/Plugin/BeaconUtils.swift +108 -40
- package/ios/Plugin/Plugin.swift +109 -128
- package/package.json +9 -3
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +0 -5
- package/android/gradle.properties +0 -24
- package/android/gradlew +0 -188
- package/android/gradlew.bat +0 -100
- package/android/proguard-rules.pro +0 -21
- package/android/settings.gradle +0 -2
- package/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java +0 -26
- package/android/src/test/java/com/getcapacitor/ExampleUnitTest.java +0 -18
- package/ios/Plugin.xcodeproj/xcuserdata/peixinliu.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
- package/ios/Plugin.xcworkspace/xcuserdata/peixinliu.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/ios/Pods/Pods.xcodeproj/xcuserdata/peixinliu.xcuserdatad/xcschemes/Capacitor.xcscheme +0 -58
- package/ios/Pods/Pods.xcodeproj/xcuserdata/peixinliu.xcuserdatad/xcschemes/CapacitorCordova.xcscheme +0 -58
- package/ios/Pods/Pods.xcodeproj/xcuserdata/peixinliu.xcuserdatad/xcschemes/Pods-Plugin.xcscheme +0 -58
- package/ios/Pods/Pods.xcodeproj/xcuserdata/peixinliu.xcuserdatad/xcschemes/Pods-PluginTests.xcscheme +0 -58
- package/ios/Pods/Pods.xcodeproj/xcuserdata/peixinliu.xcuserdatad/xcschemes/xcschememanagement.plist +0 -39
package/CHANGE-LOG.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
## 0.0.2 (2025-12-23)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
- Fix (Android): Resolved broadcast failure `ADVERTISE_FAILED_DATA_TOO_LARGE`.
|
|
5
|
+
- Fix (Android/iOS): Resolved issue where stale/historical messages were received due to system caching.
|
|
6
|
+
- Optimization: Standardized logToJsWithTag output format and logical sequence `[SEQ-XX]` .
|
|
7
|
+
|
|
8
|
+
## 0.0.3 (2025-12-23)
|
|
9
|
+
|
|
10
|
+
### Optimize code
|
|
11
|
+
- Logic Optimization: Aligned cross-platform encoding and improved variable-length payload handling.
|
|
12
|
+
- Code Fixes: Resolved naming conflicts and indexing safety issues.
|
|
13
|
+
- Log Refinement: Simplified logs with concise method tags.
|
|
@@ -10,10 +10,12 @@ import android.bluetooth.le.BluetoothLeAdvertiser;
|
|
|
10
10
|
import android.bluetooth.le.BluetoothLeScanner;
|
|
11
11
|
import android.bluetooth.le.ScanCallback;
|
|
12
12
|
import android.bluetooth.le.ScanFilter;
|
|
13
|
+
import android.bluetooth.le.ScanRecord;
|
|
13
14
|
import android.bluetooth.le.ScanResult;
|
|
14
15
|
import android.bluetooth.le.ScanSettings;
|
|
15
16
|
import android.content.Context;
|
|
16
17
|
import android.os.ParcelUuid;
|
|
18
|
+
import android.util.Log;
|
|
17
19
|
|
|
18
20
|
import com.getcapacitor.JSObject;
|
|
19
21
|
import com.getcapacitor.NativePlugin;
|
|
@@ -25,22 +27,33 @@ import java.util.ArrayList;
|
|
|
25
27
|
import java.util.List;
|
|
26
28
|
import java.util.Map;
|
|
27
29
|
|
|
28
|
-
@NativePlugin
|
|
30
|
+
@NativePlugin
|
|
29
31
|
public class Beacon extends Plugin {
|
|
30
|
-
|
|
31
|
-
private static final String
|
|
32
|
+
|
|
33
|
+
private static final String TAG = "WisdomGardenBeacon";
|
|
34
|
+
|
|
35
|
+
private static final int MANUFACTURER_ID = 0xFFFF;
|
|
32
36
|
|
|
33
37
|
private BluetoothAdapter bluetoothAdapter;
|
|
34
38
|
private BluetoothLeAdvertiser advertiser;
|
|
35
39
|
private BluetoothLeScanner scanner;
|
|
36
40
|
private boolean isBroadcasting = false;
|
|
37
41
|
private boolean isMonitoring = false;
|
|
38
|
-
private int rollcallId = 0;
|
|
39
|
-
private String nonce = "";
|
|
40
42
|
private String message = "";
|
|
41
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Helper to send tagged logs to JS bridge with sequence prefix.
|
|
46
|
+
* Consolidates bridge communication and system logging.
|
|
47
|
+
*/
|
|
48
|
+
private void logToJsWithTag(String seq, String msg) {
|
|
49
|
+
String formattedMessage = (seq != null ? " " + seq : "") + ": " + msg;
|
|
50
|
+
bridge.logToJs("[" + TAG + "]" + formattedMessage);
|
|
51
|
+
Log.i(TAG, formattedMessage);
|
|
52
|
+
}
|
|
53
|
+
|
|
42
54
|
@PluginMethod
|
|
43
55
|
public void initialize(PluginCall call) {
|
|
56
|
+
Log.i(TAG, "[SEQ-0] Plugin Initialized");
|
|
44
57
|
call.resolve();
|
|
45
58
|
}
|
|
46
59
|
|
|
@@ -51,11 +64,13 @@ public class Beacon extends Plugin {
|
|
|
51
64
|
}
|
|
52
65
|
|
|
53
66
|
if (bluetoothAdapter == null) {
|
|
67
|
+
Log.e(TAG, "Bluetooth hardware not supported on this device");
|
|
54
68
|
call.reject("Bluetooth is not supported on this device");
|
|
55
69
|
return false;
|
|
56
70
|
}
|
|
57
71
|
|
|
58
72
|
if (!bluetoothAdapter.isEnabled()) {
|
|
73
|
+
Log.w(TAG, "Bluetooth is currently disabled");
|
|
59
74
|
call.reject("Bluetooth is not enabled");
|
|
60
75
|
return false;
|
|
61
76
|
}
|
|
@@ -65,40 +80,41 @@ public class Beacon extends Plugin {
|
|
|
65
80
|
|
|
66
81
|
@PluginMethod
|
|
67
82
|
public void startBroadcasting(PluginCall call) {
|
|
68
|
-
|
|
83
|
+
logToJsWithTag("[SEQ-10]", "startBroadcasting invoked");
|
|
69
84
|
|
|
70
|
-
|
|
71
|
-
|
|
85
|
+
String inputMsg = call.getString("message");
|
|
86
|
+
if (inputMsg != null && inputMsg.length() == BeaconUtils.getMessageMaxLength()) {
|
|
87
|
+
message = inputMsg;
|
|
72
88
|
} else {
|
|
73
|
-
|
|
89
|
+
Integer rId = call.getInt("rollcallId");
|
|
90
|
+
String nonce = call.getString("nonce");
|
|
91
|
+
if (rId == null) {
|
|
92
|
+
Log.e(TAG, "Broadcasting failed: Missing rollcallId");
|
|
74
93
|
call.reject("rollcallId is required");
|
|
75
94
|
return;
|
|
76
95
|
}
|
|
77
|
-
if (
|
|
96
|
+
if (nonce == null) {
|
|
97
|
+
Log.e(TAG, "Broadcasting failed: Missing nonce");
|
|
78
98
|
call.reject("nonce is required");
|
|
79
99
|
return;
|
|
80
100
|
}
|
|
81
101
|
|
|
82
|
-
|
|
83
|
-
nonce = call.getString("nonce");
|
|
84
|
-
|
|
85
|
-
message = BeaconUtils.buildMessage(rollcallId, nonce);
|
|
102
|
+
message = BeaconUtils.buildMessage(rId, nonce);
|
|
86
103
|
}
|
|
87
|
-
|
|
88
|
-
bridge.logToJs("[Debug] startBroadcasting 11.1");
|
|
104
|
+
logToJsWithTag("[SEQ-11]", "Payload prepared: " + message);
|
|
89
105
|
|
|
90
106
|
if (isBroadcasting) {
|
|
107
|
+
logToJsWithTag("[SEQ-12]", "Already broadcasting, skipping");
|
|
91
108
|
call.resolve();
|
|
92
109
|
return;
|
|
93
110
|
}
|
|
94
|
-
bridge.logToJs("[Debug] startBroadcasting 11.2");
|
|
95
111
|
|
|
96
112
|
if (!checkBluetoothEnabled(call)) {
|
|
97
113
|
return;
|
|
98
114
|
}
|
|
99
115
|
|
|
100
116
|
startAdvertising();
|
|
101
|
-
|
|
117
|
+
logToJsWithTag("[SEQ-16]", "startBroadcasting logic completed");
|
|
102
118
|
|
|
103
119
|
call.resolve();
|
|
104
120
|
}
|
|
@@ -106,6 +122,7 @@ public class Beacon extends Plugin {
|
|
|
106
122
|
@PluginMethod
|
|
107
123
|
@SuppressLint("MissingPermission")
|
|
108
124
|
public void stopBroadcasting(PluginCall call) {
|
|
125
|
+
logToJsWithTag("[SEQ-20]", "stopBroadcasting called");
|
|
109
126
|
if (advertiser != null) {
|
|
110
127
|
advertiser.stopAdvertising(advertiseCallback);
|
|
111
128
|
advertiser = null;
|
|
@@ -118,19 +135,19 @@ public class Beacon extends Plugin {
|
|
|
118
135
|
|
|
119
136
|
@PluginMethod
|
|
120
137
|
public void startMonitoring(PluginCall call) {
|
|
121
|
-
|
|
138
|
+
logToJsWithTag("[SEQ-30]", "startMonitoring invoked");
|
|
122
139
|
if (isMonitoring) {
|
|
140
|
+
logToJsWithTag("[SEQ-31]", "Already monitoring, skipping");
|
|
123
141
|
call.resolve();
|
|
124
142
|
return;
|
|
125
143
|
}
|
|
126
|
-
bridge.logToJs("[Debug] startMonitoring 21.1");
|
|
127
144
|
|
|
128
145
|
if (!checkBluetoothEnabled(call)) {
|
|
129
146
|
return;
|
|
130
147
|
}
|
|
131
148
|
|
|
132
149
|
scanForPeripherals();
|
|
133
|
-
|
|
150
|
+
logToJsWithTag("[SEQ-36]", "startMonitoring logic completed");
|
|
134
151
|
|
|
135
152
|
call.resolve();
|
|
136
153
|
}
|
|
@@ -138,6 +155,7 @@ public class Beacon extends Plugin {
|
|
|
138
155
|
@PluginMethod
|
|
139
156
|
@SuppressLint("MissingPermission")
|
|
140
157
|
public void stopMonitoring(PluginCall call) {
|
|
158
|
+
logToJsWithTag("[SEQ-40]", "stopMonitoring called");
|
|
141
159
|
if (scanner != null) {
|
|
142
160
|
scanner.stopScan(scanCallback);
|
|
143
161
|
scanner = null;
|
|
@@ -149,6 +167,7 @@ public class Beacon extends Plugin {
|
|
|
149
167
|
@PluginMethod
|
|
150
168
|
@SuppressLint("MissingPermission")
|
|
151
169
|
public void cleanup(PluginCall call) {
|
|
170
|
+
Log.i(TAG, "[SEQ-90] Cleanup: Resetting all states");
|
|
152
171
|
if (advertiser != null) {
|
|
153
172
|
advertiser.stopAdvertising(advertiseCallback);
|
|
154
173
|
advertiser = null;
|
|
@@ -165,8 +184,6 @@ public class Beacon extends Plugin {
|
|
|
165
184
|
isMonitoring = false;
|
|
166
185
|
|
|
167
186
|
message = "";
|
|
168
|
-
nonce = "";
|
|
169
|
-
rollcallId = 0;
|
|
170
187
|
|
|
171
188
|
removeAllListeners(call);
|
|
172
189
|
|
|
@@ -176,6 +193,7 @@ public class Beacon extends Plugin {
|
|
|
176
193
|
@PluginMethod
|
|
177
194
|
public void parseMessage(PluginCall call) {
|
|
178
195
|
String message = call.getString("message");
|
|
196
|
+
Log.v(TAG, "Parsing message: " + message);
|
|
179
197
|
Map<String, Object> result = BeaconUtils.parseMessage(message);
|
|
180
198
|
JSObject jsResult = new JSObject();
|
|
181
199
|
jsResult.put("rollcallId", result.get("rollcallId"));
|
|
@@ -185,7 +203,7 @@ public class Beacon extends Plugin {
|
|
|
185
203
|
|
|
186
204
|
@SuppressLint("MissingPermission")
|
|
187
205
|
private void startAdvertising() {
|
|
188
|
-
|
|
206
|
+
logToJsWithTag("[SEQ-13]", "Initialising LE Advertiser");
|
|
189
207
|
if (advertiser == null) {
|
|
190
208
|
advertiser = bluetoothAdapter.getBluetoothLeAdvertiser();
|
|
191
209
|
}
|
|
@@ -193,7 +211,7 @@ public class Beacon extends Plugin {
|
|
|
193
211
|
if (isBroadcasting) {
|
|
194
212
|
return;
|
|
195
213
|
}
|
|
196
|
-
|
|
214
|
+
logToJsWithTag("[SEQ-13-1]", "Preparing packet with message: " + message);
|
|
197
215
|
isBroadcasting = true;
|
|
198
216
|
|
|
199
217
|
AdvertiseSettings settings = new AdvertiseSettings.Builder()
|
|
@@ -203,24 +221,22 @@ public class Beacon extends Plugin {
|
|
|
203
221
|
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM)
|
|
204
222
|
.build();
|
|
205
223
|
|
|
206
|
-
|
|
207
|
-
bluetoothAdapter.setName(message);
|
|
208
|
-
bridge.logToJs("[Debug] startAdvertising broadcasting 13.1 " + message + " " + message.length());
|
|
224
|
+
byte[] payload = BeaconUtils.addPrefixToBytes(message);
|
|
209
225
|
|
|
210
226
|
AdvertiseData advertiseData = new AdvertiseData.Builder()
|
|
211
|
-
.setIncludeDeviceName(
|
|
227
|
+
.setIncludeDeviceName(false)
|
|
212
228
|
.setIncludeTxPowerLevel(false)
|
|
213
|
-
.addServiceUuid(new ParcelUuid(java.util.UUID.fromString(WG_UUID)))
|
|
229
|
+
.addServiceUuid(new ParcelUuid(java.util.UUID.fromString(BeaconUtils.WG_UUID)))
|
|
230
|
+
.addManufacturerData(MANUFACTURER_ID, payload)
|
|
214
231
|
.build();
|
|
215
232
|
|
|
216
|
-
|
|
233
|
+
logToJsWithTag("[SEQ-14]", "Calling advertiser.startAdvertising");
|
|
217
234
|
advertiser.startAdvertising(settings, advertiseData, advertiseCallback);
|
|
218
|
-
bridge.logToJs("[Debug] startAdvertising broadcasting 15");
|
|
219
235
|
}
|
|
220
236
|
|
|
221
237
|
@SuppressLint("MissingPermission")
|
|
222
238
|
private void scanForPeripherals() {
|
|
223
|
-
|
|
239
|
+
logToJsWithTag("[SEQ-32]", "Initialising LE Scanner");
|
|
224
240
|
if (scanner == null) {
|
|
225
241
|
scanner = bluetoothAdapter.getBluetoothLeScanner();
|
|
226
242
|
}
|
|
@@ -228,30 +244,24 @@ public class Beacon extends Plugin {
|
|
|
228
244
|
if (isMonitoring) {
|
|
229
245
|
return;
|
|
230
246
|
}
|
|
231
|
-
|
|
247
|
+
|
|
232
248
|
isMonitoring = true;
|
|
233
249
|
|
|
234
|
-
ScanSettings settings = new ScanSettings.Builder()
|
|
235
|
-
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
|
|
236
|
-
.setReportDelay(0)
|
|
237
|
-
.build();
|
|
250
|
+
ScanSettings settings = new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).setReportDelay(0).build();
|
|
238
251
|
|
|
239
252
|
List<ScanFilter> filters = new ArrayList<>();
|
|
240
|
-
ScanFilter filter = new ScanFilter.Builder()
|
|
241
|
-
.setServiceUuid(new ParcelUuid(java.util.UUID.fromString(WG_UUID)))
|
|
242
|
-
.build();
|
|
253
|
+
ScanFilter filter = new ScanFilter.Builder().setServiceUuid(new ParcelUuid(java.util.UUID.fromString(BeaconUtils.WG_UUID))).build();
|
|
243
254
|
filters.add(filter);
|
|
244
255
|
|
|
245
|
-
|
|
256
|
+
logToJsWithTag("[SEQ-33]", "Calling scanner.startScan");
|
|
246
257
|
scanner.startScan(filters, settings, scanCallback);
|
|
247
|
-
bridge.logToJs("[Debug] scanForPeripherals monitoring 25");
|
|
248
258
|
}
|
|
249
259
|
|
|
250
260
|
private final AdvertiseCallback advertiseCallback = new AdvertiseCallback() {
|
|
251
261
|
@Override
|
|
252
262
|
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
|
|
253
263
|
super.onStartSuccess(settingsInEffect);
|
|
254
|
-
|
|
264
|
+
logToJsWithTag("[SEQ-15]", "AdvertiseCallback: Success (Peripheral Active)");
|
|
255
265
|
JSObject data = new JSObject();
|
|
256
266
|
data.put("type", "peripheral");
|
|
257
267
|
data.put("state", "poweredOn");
|
|
@@ -261,7 +271,7 @@ public class Beacon extends Plugin {
|
|
|
261
271
|
@Override
|
|
262
272
|
public void onStartFailure(int errorCode) {
|
|
263
273
|
super.onStartFailure(errorCode);
|
|
264
|
-
|
|
274
|
+
logToJsWithTag("[SEQ-15-F]", "AdvertiseCallback: Failure (Error: " + errorCode + ")");
|
|
265
275
|
JSObject data = new JSObject();
|
|
266
276
|
data.put("type", "peripheral");
|
|
267
277
|
data.put("state", errorCode);
|
|
@@ -275,20 +285,52 @@ public class Beacon extends Plugin {
|
|
|
275
285
|
@Override
|
|
276
286
|
public void onScanResult(int callbackType, ScanResult result) {
|
|
277
287
|
super.onScanResult(callbackType, result);
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
288
|
+
ScanRecord record = result.getScanRecord();
|
|
289
|
+
Log.d(TAG, "start scan result received");
|
|
290
|
+
if (record == null) {
|
|
291
|
+
Log.w(TAG, "Scan result is null");
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
String finalMsg;
|
|
296
|
+
/*
|
|
297
|
+
* [SEQ-50]
|
|
298
|
+
* Strategy 1: Attempt to extract message from Manufacturer Specific Data.
|
|
299
|
+
* This is the primary channel, typically used by Android broadcasters.
|
|
300
|
+
* The data is retrieved using the 0xFFFF Company ID.
|
|
301
|
+
*/
|
|
302
|
+
byte[] vendorData = record.getManufacturerSpecificData(MANUFACTURER_ID);
|
|
303
|
+
finalMsg = BeaconUtils.extractMessageFromBytes(vendorData);
|
|
304
|
+
|
|
305
|
+
/*
|
|
306
|
+
* [SEQ-51]
|
|
307
|
+
* Strategy 2: Fallback to Device Name if Manufacturer Data is invalid or missing.
|
|
308
|
+
* This channel is used to support iOS broadcasters, which advertise the
|
|
309
|
+
* message within the Scan Response's Local Name field.
|
|
310
|
+
*/
|
|
311
|
+
if (finalMsg == null) {
|
|
312
|
+
String deviceName = record.getDeviceName();
|
|
313
|
+
Log.d(TAG, "Valid message found in Device Name: " + deviceName);
|
|
314
|
+
String deviceMessage = BeaconUtils.extractMessageFromDeviceName(deviceName);
|
|
315
|
+
if (deviceMessage != null) {
|
|
316
|
+
finalMsg = deviceMessage;
|
|
290
317
|
}
|
|
291
318
|
}
|
|
319
|
+
/*
|
|
320
|
+
* Post-Processing: If a valid protocol message is identified, package the
|
|
321
|
+
* metadata (RSSI, MAC address, and timestamp) and notify the JavaScript layer.
|
|
322
|
+
*/
|
|
323
|
+
if (finalMsg != null) {
|
|
324
|
+
logToJsWithTag("[SEQ-55]", "Beacon Resolved: " + finalMsg + " | RSSI: " + result.getRssi());
|
|
325
|
+
JSObject jsResult = new JSObject();
|
|
326
|
+
jsResult.put("message", finalMsg);
|
|
327
|
+
jsResult.put("rssi", result.getRssi());
|
|
328
|
+
jsResult.put("peripheralId", result.getDevice().getAddress());
|
|
329
|
+
jsResult.put("timestamp", System.currentTimeMillis());
|
|
330
|
+
notifyListeners("beaconReceived", jsResult);
|
|
331
|
+
} else {
|
|
332
|
+
logToJsWithTag("[SEQ-55-F]", "No valid message found in scan result");
|
|
333
|
+
}
|
|
292
334
|
}
|
|
293
335
|
};
|
|
294
|
-
}
|
|
336
|
+
}
|
|
@@ -1,10 +1,41 @@
|
|
|
1
1
|
package com.wisdomgarden.mobile.beacon;
|
|
2
2
|
|
|
3
|
+
import android.util.Log;
|
|
4
|
+
import java.nio.charset.StandardCharsets;
|
|
3
5
|
import java.util.HashMap;
|
|
4
6
|
import java.util.Map;
|
|
5
|
-
import java.util.Random;
|
|
6
7
|
|
|
7
8
|
public class BeaconUtils {
|
|
9
|
+
|
|
10
|
+
private static final String TAG = "BeaconUtils";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 16-bit Bluetooth Short UUID (0x5747)
|
|
14
|
+
* Logic & Rationale:
|
|
15
|
+
* 1. 'W' hex value is 0x57, 'G' hex value is 0x47.
|
|
16
|
+
* 2. Formatted according to the Bluetooth SIG Base UUID: 0000XXXX-0000-1000-8000-00805F9B34FB.
|
|
17
|
+
* 3. Replacing XXXX with 5747 results in this full string.
|
|
18
|
+
* 4. Using a 16-bit UUID reduces the UUID overhead in the broadcast packet from 18 bytes
|
|
19
|
+
* to 4 bytes, effectively fixing the 'ADVERTISE_FAILED_DATA_TOO_LARGE' error by
|
|
20
|
+
* staying well within the 31-byte BLE Legacy Advertising limit.
|
|
21
|
+
*/
|
|
22
|
+
public static final String WG_UUID = "00005747-0000-1000-8000-00805F9B34FB";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Identification prefix for string-based payloads (Device Name).
|
|
26
|
+
* This prefix is used by iOS when advertising the message via the Device Name field.
|
|
27
|
+
* Both platforms use this string to identify and validate Wisdom Garden
|
|
28
|
+
* peripherals during the scanning process.
|
|
29
|
+
*/
|
|
30
|
+
public static final String WG_PREFIX = "WG";
|
|
31
|
+
/**
|
|
32
|
+
* Hexadecimal representation of "WG" (0x57, 0x47).
|
|
33
|
+
* This prefix is specifically designed for Android's ManufacturerData field.
|
|
34
|
+
* It serves as a protocol identifier within the raw byte payload to
|
|
35
|
+
* distinguish our packets from other 0xFFFF manufacturer data.
|
|
36
|
+
*/
|
|
37
|
+
private static final byte[] WG_PREFIX_BYTES = new byte[] { 0x57, 0x47 };
|
|
38
|
+
|
|
8
39
|
private static final String CHARS = "2O9AuFNPDx4gtJwS3ye7l1Mq0dEB5HsaKInikRmLhjpG6b8fzQrCcvo";
|
|
9
40
|
private static final int RADIX = CHARS.length();
|
|
10
41
|
private static final int MESSAGE_MAX_LENGTH = 11;
|
|
@@ -16,10 +47,9 @@ public class BeaconUtils {
|
|
|
16
47
|
* @return 编码后的字符串
|
|
17
48
|
*/
|
|
18
49
|
public static String encode(int num) {
|
|
19
|
-
if (num
|
|
50
|
+
if (num <= 0) return String.valueOf(CHARS.charAt(0));
|
|
20
51
|
StringBuilder result = new StringBuilder();
|
|
21
|
-
|
|
22
|
-
int n = Math.abs(num);
|
|
52
|
+
int n = num;
|
|
23
53
|
|
|
24
54
|
while (n > 0) {
|
|
25
55
|
int remainder = n % RADIX;
|
|
@@ -27,7 +57,7 @@ public class BeaconUtils {
|
|
|
27
57
|
n /= RADIX;
|
|
28
58
|
}
|
|
29
59
|
|
|
30
|
-
return
|
|
60
|
+
return result.toString();
|
|
31
61
|
}
|
|
32
62
|
|
|
33
63
|
/**
|
|
@@ -56,8 +86,7 @@ public class BeaconUtils {
|
|
|
56
86
|
* @return 如果是 nonce 字母返回 true,否则返回 false
|
|
57
87
|
*/
|
|
58
88
|
public static boolean isNonceLetter(char c) {
|
|
59
|
-
|
|
60
|
-
return code >= 84 && code <= 90;
|
|
89
|
+
return c >= 'T' && c <= 'Z';
|
|
61
90
|
}
|
|
62
91
|
|
|
63
92
|
/**
|
|
@@ -89,18 +118,16 @@ public class BeaconUtils {
|
|
|
89
118
|
payload.put("rollcallId", 0);
|
|
90
119
|
payload.put("nonce", "");
|
|
91
120
|
|
|
92
|
-
if (message == null) {
|
|
121
|
+
if (message == null || message.isEmpty()) {
|
|
93
122
|
return payload;
|
|
94
123
|
}
|
|
95
124
|
|
|
96
|
-
|
|
97
125
|
for (int i = 0; i < message.length(); i++) {
|
|
98
|
-
|
|
99
|
-
if (isNonceLetter(c)) {
|
|
126
|
+
if (isNonceLetter(message.charAt(i))) {
|
|
100
127
|
String rollcallIdStr = message.substring(0, i);
|
|
101
128
|
payload.put("rollcallId", decode(rollcallIdStr));
|
|
102
129
|
payload.put("nonce", message.substring(i));
|
|
103
|
-
|
|
130
|
+
return payload;
|
|
104
131
|
}
|
|
105
132
|
}
|
|
106
133
|
|
|
@@ -115,4 +142,74 @@ public class BeaconUtils {
|
|
|
115
142
|
public static int getMessageMaxLength() {
|
|
116
143
|
return MESSAGE_MAX_LENGTH;
|
|
117
144
|
}
|
|
118
|
-
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Constructs the raw byte array for Android's Manufacturer Data.
|
|
148
|
+
* It prepends the "WG" hexadecimal prefix (0x57, 0x47) to the ASCII-encoded message.
|
|
149
|
+
* This format ensures that the data is recognizable as a "Wisdom Garden" packet
|
|
150
|
+
* when transmitted within a raw byte stream.
|
|
151
|
+
*/
|
|
152
|
+
public static byte[] addPrefixToBytes(String message) {
|
|
153
|
+
Log.d(TAG, "[addPrefixToBytes]Adding hex prefix to message: " + message);
|
|
154
|
+
|
|
155
|
+
byte[] msgBytes = message.getBytes(StandardCharsets.US_ASCII);
|
|
156
|
+
byte[] data = new byte[WG_PREFIX_BYTES.length + msgBytes.length];
|
|
157
|
+
System.arraycopy(WG_PREFIX_BYTES, 0, data, 0, WG_PREFIX_BYTES.length);
|
|
158
|
+
System.arraycopy(msgBytes, 0, data, WG_PREFIX_BYTES.length, msgBytes.length);
|
|
159
|
+
|
|
160
|
+
Log.v(TAG, "[addPrefixToBytes]Resulting byte array length: " + data.length);
|
|
161
|
+
return data;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Extracts and validates the message from raw Manufacturer Data bytes.
|
|
166
|
+
* The method verifies the "WG" hexadecimal prefix and ensures the total
|
|
167
|
+
* length matches the protocol specification before returning the decoded string.
|
|
168
|
+
*/
|
|
169
|
+
public static String extractMessageFromBytes(byte[] data) {
|
|
170
|
+
if (data == null) {
|
|
171
|
+
Log.w(TAG, "[extractBytes] data is null");
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Total length must be: length of WG_PREFIX_BYTES + length of message
|
|
176
|
+
int expectedLength = WG_PREFIX_BYTES.length + MESSAGE_MAX_LENGTH;
|
|
177
|
+
|
|
178
|
+
if (data.length < WG_PREFIX_BYTES.length || data.length > expectedLength) {
|
|
179
|
+
Log.w(TAG, "[extractBytes] Length out of bounds. Max: " + expectedLength + ", Got: " + data.length);
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
for (int i = 0; i < WG_PREFIX_BYTES.length; i++) {
|
|
183
|
+
if (data[i] != WG_PREFIX_BYTES[i]) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
int payloadLength = data.length - WG_PREFIX_BYTES.length;
|
|
188
|
+
String extracted = new String(data, WG_PREFIX_BYTES.length, payloadLength, StandardCharsets.US_ASCII);
|
|
189
|
+
Log.i(TAG, "[extractBytes] Successfully extracted message from bytes: " + extracted);
|
|
190
|
+
return extracted;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Extracts the message payload from a scanned Device Name string.
|
|
195
|
+
* Specifically designed for iOS-to-Android or iOS-to-iOS communication,
|
|
196
|
+
* where the message is prefixed with "WG" and advertised as the device's name.
|
|
197
|
+
*/
|
|
198
|
+
public static String extractMessageFromDeviceName(String message) {
|
|
199
|
+
if (message == null || message.isEmpty()) {
|
|
200
|
+
Log.w(TAG, "[extractName] message is null");
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
if (!message.startsWith(WG_PREFIX)) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
int expectedLength = WG_PREFIX.length() + MESSAGE_MAX_LENGTH;
|
|
207
|
+
if (message.length() > expectedLength) {
|
|
208
|
+
Log.w(TAG, "[extractName] Length out of bounds. Max: " + expectedLength + ", Got: " + message.length());
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
String extracted = message.substring(WG_PREFIX.length());
|
|
212
|
+
Log.i(TAG, "[extractName] Successfully. " + extracted);
|
|
213
|
+
return extracted;
|
|
214
|
+
}
|
|
215
|
+
}
|