@mentra/bluetooth-sdk 0.1.18 → 0.1.20
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 +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +135 -51
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +120 -17
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +5 -6
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +34 -21
- package/android/src/main/java/com/mentra/bluetoothsdk/OtaManifest.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +13 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1TextSanitizer.kt +38 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +488 -15
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +551 -159
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLiveL2capChannel.kt +274 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +361 -67
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraosBle.java +7501 -3704
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +134 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +0 -6
- package/android/src/main/java/com/mentra/bluetoothsdk/types/DeviceModels.kt +2 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BleJsonCompact.java +493 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadService.java +65 -5
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BleWireProtocol.java +108 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/IncidentLogBleUploadService.java +4 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/K900LengthCodec.java +115 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtils.java +109 -73
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunkReassembler.java +106 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunker.java +93 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/NexSGCUtils.kt +111 -11
- package/android/src/test/java/com/mentra/bluetoothsdk/BluetoothSdkExceptionTest.kt +18 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/camera/PhotoRequestTest.kt +23 -9
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/G1TextSanitizerTest.kt +31 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/AvifExifStripperTest.java +17 -1
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/BinaryMessageChunkerTest.java +84 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/BleJsonCompactTest.java +158 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadServiceTest.java +9 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtilsEndiannessTest.java +172 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtilsTest.java +24 -0
- package/build/BluetoothSdk.types.d.ts +37 -1
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +4 -0
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/_private/photoRequestPayload.d.ts.map +1 -1
- package/build/_private/photoRequestPayload.js +1 -0
- package/build/_private/photoRequestPayload.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +5 -0
- package/build/index.js.map +1 -1
- package/build/types/index.d.ts +3 -0
- package/build/types/index.d.ts.map +1 -0
- package/build/types/index.js +2 -0
- package/build/types/index.js.map +1 -0
- package/ios/Source/BluetoothSdkDefaults.swift +1 -1
- package/ios/Source/DeviceManager.swift +136 -30
- package/ios/Source/DeviceStore.swift +5 -5
- package/ios/Source/MentraBluetoothSDK.swift +40 -24
- package/ios/Source/OtaManifest.swift +1 -1
- package/ios/Source/camera/CameraModels.swift +17 -3
- package/ios/Source/sgcs/G1.swift +1 -1
- package/ios/Source/sgcs/G2.swift +518 -11
- package/ios/Source/sgcs/MentraLive.swift +443 -42
- package/ios/Source/sgcs/MentraLiveL2capChannel.swift +183 -0
- package/ios/Source/sgcs/MentraNex.swift +416 -32
- package/ios/Source/sgcs/SGCManager.swift +155 -0
- package/ios/Source/sgcs/mentraos_ble.pb.swift +726 -122
- package/ios/Source/status/DeviceStatus.swift +0 -8
- package/ios/Source/utils/BleJsonCompact.swift +395 -0
- package/ios/Source/utils/BleWireProtocol.swift +92 -0
- package/ios/Source/utils/G1Text.swift +30 -1
- package/ios/Source/utils/MessageChunkReassembler.swift +82 -0
- package/ios/Source/utils/MessageChunker.swift +73 -0
- package/package.json +14 -7
- package/src/BluetoothSdk.types.ts +39 -1
- package/src/_private/BluetoothSdkModule.ts +4 -0
- package/src/_private/photoRequestPayload.ts +1 -0
- package/src/index.ts +6 -0
- package/src/types/index.ts +7 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
package com.mentra.bluetoothsdk.utils;
|
|
2
|
+
|
|
3
|
+
import java.nio.charset.StandardCharsets;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* BLE Wire Protocol v2 shared constants and binary frame helpers.
|
|
7
|
+
* All multi-byte fields use little-endian byte order.
|
|
8
|
+
*/
|
|
9
|
+
public final class BleWireProtocol {
|
|
10
|
+
|
|
11
|
+
private BleWireProtocol() {}
|
|
12
|
+
|
|
13
|
+
public static final byte CMD_TYPE_BINARY_MSG = 0x40;
|
|
14
|
+
|
|
15
|
+
public static final byte BLE_WIRE_FLAG_FIRST_FRAG = 0x01;
|
|
16
|
+
public static final byte BLE_WIRE_FLAG_LAST_FRAG = 0x02;
|
|
17
|
+
public static final byte BLE_WIRE_FLAG_WAKE = 0x04;
|
|
18
|
+
public static final byte BLE_WIRE_FLAG_HANDSHAKE = 0x08;
|
|
19
|
+
public static final byte BLE_WIRE_FLAG_ACK_REQUESTED = 0x10;
|
|
20
|
+
|
|
21
|
+
public static final int BINARY_HEADER_SIZE = 7;
|
|
22
|
+
public static final int MAX_FRAGMENT_PAYLOAD = 480;
|
|
23
|
+
public static final int MTU_TARGET = 509;
|
|
24
|
+
|
|
25
|
+
public static final String HANDSHAKE_PAYLOAD_V2 = "v2";
|
|
26
|
+
public static final int PROTOCOL_V1 = 1;
|
|
27
|
+
public static final int PROTOCOL_V2 = 2;
|
|
28
|
+
|
|
29
|
+
public static class BinaryFragmentInfo {
|
|
30
|
+
public byte flags;
|
|
31
|
+
public int msgId;
|
|
32
|
+
public int fragIdx;
|
|
33
|
+
public int fragCount;
|
|
34
|
+
public byte[] payload;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public static boolean isBinaryFrame(byte[] data) {
|
|
38
|
+
return K900ProtocolUtils.isK900ProtocolFormat(data)
|
|
39
|
+
&& data.length >= 7
|
|
40
|
+
&& data[2] == CMD_TYPE_BINARY_MSG;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public static byte[] packBinaryFragment(
|
|
44
|
+
byte flags, int msgId, int fragIdx, int fragCount, byte[] payload) {
|
|
45
|
+
int payloadLen = payload != null ? payload.length : 0;
|
|
46
|
+
byte[] inner = new byte[BINARY_HEADER_SIZE + payloadLen];
|
|
47
|
+
inner[0] = flags;
|
|
48
|
+
writeLe16(inner, 1, msgId);
|
|
49
|
+
inner[3] = (byte) fragIdx;
|
|
50
|
+
inner[4] = (byte) fragCount;
|
|
51
|
+
writeLe16(inner, 5, payloadLen);
|
|
52
|
+
if (payloadLen > 0) {
|
|
53
|
+
System.arraycopy(payload, 0, inner, BINARY_HEADER_SIZE, payloadLen);
|
|
54
|
+
}
|
|
55
|
+
return K900ProtocolUtils.packDataToK900(
|
|
56
|
+
inner, CMD_TYPE_BINARY_MSG, K900LengthCodec.Endian.LE);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public static BinaryFragmentInfo extractBinaryFragmentInfo(byte[] frame) {
|
|
60
|
+
if (!isBinaryFrame(frame)) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
int innerLen = readLe16(frame, 3);
|
|
65
|
+
if (innerLen < BINARY_HEADER_SIZE || frame.length < 7 + innerLen) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
int offset = 5;
|
|
70
|
+
int payloadLen = readLe16(frame, offset + 5);
|
|
71
|
+
if (BINARY_HEADER_SIZE + payloadLen > innerLen) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
BinaryFragmentInfo info = new BinaryFragmentInfo();
|
|
76
|
+
info.flags = frame[offset];
|
|
77
|
+
info.msgId = readLe16(frame, offset + 1);
|
|
78
|
+
info.fragIdx = frame[offset + 3] & 0xFF;
|
|
79
|
+
info.fragCount = frame[offset + 4] & 0xFF;
|
|
80
|
+
info.payload = new byte[payloadLen];
|
|
81
|
+
if (payloadLen > 0) {
|
|
82
|
+
System.arraycopy(frame, offset + BINARY_HEADER_SIZE, info.payload, 0, payloadLen);
|
|
83
|
+
}
|
|
84
|
+
return info;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public static byte[] extractBinaryPayload(byte[] frame) {
|
|
88
|
+
BinaryFragmentInfo info = extractBinaryFragmentInfo(frame);
|
|
89
|
+
return info != null ? info.payload : null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public static boolean isHandshakeV2(BinaryFragmentInfo info) {
|
|
93
|
+
if (info == null || (info.flags & BLE_WIRE_FLAG_HANDSHAKE) == 0) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
String payload = new String(info.payload, StandardCharsets.UTF_8);
|
|
97
|
+
return HANDSHAKE_PAYLOAD_V2.equals(payload);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
static int readLe16(byte[] data, int offset) {
|
|
101
|
+
return (data[offset] & 0xFF) | ((data[offset + 1] & 0xFF) << 8);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
static void writeLe16(byte[] data, int offset, int value) {
|
|
105
|
+
data[offset] = (byte) (value & 0xFF);
|
|
106
|
+
data[offset + 1] = (byte) ((value >> 8) & 0xFF);
|
|
107
|
+
}
|
|
108
|
+
}
|
package/android/src/main/java/com/mentra/bluetoothsdk/utils/IncidentLogBleUploadService.java
CHANGED
|
@@ -13,7 +13,7 @@ import okhttp3.RequestBody;
|
|
|
13
13
|
import okhttp3.Response;
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* POSTs
|
|
16
|
+
* POSTs report artifact JSON from the BLE relay using the same path as glasses direct upload.
|
|
17
17
|
*/
|
|
18
18
|
public final class IncidentLogBleUploadService {
|
|
19
19
|
|
|
@@ -42,9 +42,10 @@ public final class IncidentLogBleUploadService {
|
|
|
42
42
|
}
|
|
43
43
|
HttpUrl url = baseHttpUrl.newBuilder()
|
|
44
44
|
.addPathSegment("api")
|
|
45
|
-
.addPathSegment("
|
|
45
|
+
.addPathSegment("client")
|
|
46
|
+
.addPathSegment("reports")
|
|
46
47
|
.addPathSegment(incidentId)
|
|
47
|
-
.addPathSegment("
|
|
48
|
+
.addPathSegment("artifacts")
|
|
48
49
|
.build();
|
|
49
50
|
RequestBody body = RequestBody.create(jsonUtf8, JSON);
|
|
50
51
|
OkHttpClient client = new OkHttpClient.Builder()
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
package com.mentra.bluetoothsdk.utils;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Encodes and decodes the 2-byte length field of K900 {@code ##} STRING (0x30) frames.
|
|
5
|
+
*
|
|
6
|
+
* <p>Legacy BES firmware uses big-endian for this field; BLE Wire v2 clients use little-endian.
|
|
7
|
+
* The two conventions are negotiated per link (see {@code wire_caps}); until a peer advertises
|
|
8
|
+
* little-endian, callers should default to {@link Endian#BE}. On receive, {@link #detectLength}
|
|
9
|
+
* heuristically recovers the endianness so we interoperate with peers of unknown vintage.
|
|
10
|
+
*
|
|
11
|
+
* <p>File-transfer header fields (packSize/packIndex/fileSize) are always big-endian and are not
|
|
12
|
+
* handled here.
|
|
13
|
+
*
|
|
14
|
+
* <p>Mirror of the ASG-side {@code K900LengthCodec} and firmware {@code k900_detect_string_length}.
|
|
15
|
+
*/
|
|
16
|
+
public final class K900LengthCodec {
|
|
17
|
+
|
|
18
|
+
private K900LengthCodec() {}
|
|
19
|
+
|
|
20
|
+
public enum Endian {
|
|
21
|
+
BE,
|
|
22
|
+
LE
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public static final int FRAME_OVERHEAD = 7;
|
|
26
|
+
public static final int LENGTH_OFFSET = 3;
|
|
27
|
+
public static final int PAYLOAD_OFFSET = 5;
|
|
28
|
+
public static final int MAX_STRING_LENGTH = 512;
|
|
29
|
+
|
|
30
|
+
public static int readLength(byte[] frame, int offset, Endian endian) {
|
|
31
|
+
int b0 = frame[offset] & 0xFF;
|
|
32
|
+
int b1 = frame[offset + 1] & 0xFF;
|
|
33
|
+
return endian == Endian.LE ? (b0 | (b1 << 8)) : ((b0 << 8) | b1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public static void writeLength(byte[] frame, int offset, int value, Endian endian) {
|
|
37
|
+
if (endian == Endian.LE) {
|
|
38
|
+
frame[offset] = (byte) (value & 0xFF);
|
|
39
|
+
frame[offset + 1] = (byte) ((value >> 8) & 0xFF);
|
|
40
|
+
} else {
|
|
41
|
+
frame[offset] = (byte) ((value >> 8) & 0xFF);
|
|
42
|
+
frame[offset + 1] = (byte) (value & 0xFF);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public static final class Detected {
|
|
47
|
+
public final int length;
|
|
48
|
+
public final Endian endian;
|
|
49
|
+
|
|
50
|
+
Detected(int length, Endian endian) {
|
|
51
|
+
this.length = length;
|
|
52
|
+
this.endian = endian;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Heuristically determine the STRING length field's endianness for a received frame. A length is
|
|
58
|
+
* plausible when it is non-zero, within the cap, and the full framed command fits inside the
|
|
59
|
+
* buffer. When both interpretations fit, prefer the one whose payload lands exactly on the
|
|
60
|
+
* trailing {@code $$}, then the smaller.
|
|
61
|
+
*
|
|
62
|
+
* @return the detected length + endianness, or {@code null} if neither interpretation is valid
|
|
63
|
+
*/
|
|
64
|
+
public static Detected detectLength(byte[] frame) {
|
|
65
|
+
if (frame == null || frame.length < FRAME_OVERHEAD) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
int leLen = readLength(frame, LENGTH_OFFSET, Endian.LE);
|
|
70
|
+
int beLen = readLength(frame, LENGTH_OFFSET, Endian.BE);
|
|
71
|
+
|
|
72
|
+
boolean leOk = leLen > 0 && leLen <= MAX_STRING_LENGTH && leLen + FRAME_OVERHEAD <= frame.length;
|
|
73
|
+
boolean beOk = beLen > 0 && beLen <= MAX_STRING_LENGTH && beLen + FRAME_OVERHEAD <= frame.length;
|
|
74
|
+
|
|
75
|
+
if (leOk && beOk) {
|
|
76
|
+
if (endsWithEndCode(frame, PAYLOAD_OFFSET + leLen)) {
|
|
77
|
+
return new Detected(leLen, Endian.LE);
|
|
78
|
+
}
|
|
79
|
+
if (endsWithEndCode(frame, PAYLOAD_OFFSET + beLen)) {
|
|
80
|
+
return new Detected(beLen, Endian.BE);
|
|
81
|
+
}
|
|
82
|
+
return leLen <= beLen ? new Detected(leLen, Endian.LE) : new Detected(beLen, Endian.BE);
|
|
83
|
+
}
|
|
84
|
+
if (leOk) {
|
|
85
|
+
return new Detected(leLen, Endian.LE);
|
|
86
|
+
}
|
|
87
|
+
if (beOk) {
|
|
88
|
+
return new Detected(beLen, Endian.BE);
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private static boolean endsWithEndCode(byte[] frame, int endOffset) {
|
|
94
|
+
return endOffset + 1 < frame.length
|
|
95
|
+
&& frame[endOffset] == K900ProtocolUtils.CMD_END_CODE[0]
|
|
96
|
+
&& frame[endOffset + 1] == K900ProtocolUtils.CMD_END_CODE[1];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Transcode a K900 STRING frame's length bytes from one endianness to another, leaving the
|
|
101
|
+
* payload untouched. Returns a new array; the input is not modified.
|
|
102
|
+
*/
|
|
103
|
+
public static byte[] repackStringFrame(byte[] frame, Endian from, Endian to) {
|
|
104
|
+
if (frame == null || frame.length < FRAME_OVERHEAD) {
|
|
105
|
+
return frame;
|
|
106
|
+
}
|
|
107
|
+
if (from == to) {
|
|
108
|
+
return frame.clone();
|
|
109
|
+
}
|
|
110
|
+
byte[] out = frame.clone();
|
|
111
|
+
int length = readLength(frame, LENGTH_OFFSET, from);
|
|
112
|
+
writeLength(out, LENGTH_OFFSET, length, to);
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -27,9 +27,12 @@ public class K900ProtocolUtils {
|
|
|
27
27
|
public static final byte CMD_TYPE_MUSIC = 0x33; // Music file type
|
|
28
28
|
public static final byte CMD_TYPE_AUDIO = 0x34; // Audio file type
|
|
29
29
|
public static final byte CMD_TYPE_DATA = 0x35; // Generic data type
|
|
30
|
+
public static final byte CMD_TYPE_BINARY_MSG = BleWireProtocol.CMD_TYPE_BINARY_MSG;
|
|
30
31
|
|
|
31
32
|
// File transfer constants
|
|
32
|
-
|
|
33
|
+
// Negotiated file protocol ceiling. Legacy/GATT transfers remain smaller; 800-byte frames are
|
|
34
|
+
// accepted only after file_payload_v2 negotiation and an open CoC channel.
|
|
35
|
+
public static final int FILE_PACK_SIZE = 800;
|
|
33
36
|
public static final int LENGTH_FILE_START = 2;
|
|
34
37
|
public static final int LENGTH_FILE_TYPE = 1;
|
|
35
38
|
public static final int LENGTH_FILE_PACKSIZE = 2;
|
|
@@ -54,6 +57,10 @@ public class K900ProtocolUtils {
|
|
|
54
57
|
* @return Byte array with packed data according to protocol format
|
|
55
58
|
*/
|
|
56
59
|
public static byte[] packJsonCommand(String jsonData) {
|
|
60
|
+
return packJsonCommand(jsonData, K900LengthCodec.Endian.BE);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public static byte[] packJsonCommand(String jsonData, K900LengthCodec.Endian endian) {
|
|
57
64
|
if (jsonData == null) {
|
|
58
65
|
return null;
|
|
59
66
|
}
|
|
@@ -68,7 +75,7 @@ public class K900ProtocolUtils {
|
|
|
68
75
|
|
|
69
76
|
// Then pack with BES2700 protocol format
|
|
70
77
|
byte[] jsonBytes = wrappedJson.getBytes(StandardCharsets.UTF_8);
|
|
71
|
-
return packDataCommand(jsonBytes, CMD_TYPE_STRING);
|
|
78
|
+
return packDataCommand(jsonBytes, CMD_TYPE_STRING, endian);
|
|
72
79
|
|
|
73
80
|
} catch (JSONException e) {
|
|
74
81
|
android.util.Log.e("K900ProtocolUtils", "Error creating JSON wrapper", e);
|
|
@@ -85,6 +92,16 @@ public class K900ProtocolUtils {
|
|
|
85
92
|
* @return Byte array with packed data according to protocol format
|
|
86
93
|
*/
|
|
87
94
|
public static byte[] packDataCommand(byte[] data, byte cmdType) {
|
|
95
|
+
// Default to legacy big-endian until wire_caps negotiates little-endian.
|
|
96
|
+
return packDataCommand(data, cmdType, K900LengthCodec.Endian.BE);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Pack raw byte data with K900 BES2700 protocol format, writing the 2-byte length field with an
|
|
101
|
+
* explicit endianness. Use the negotiated per-link endianness so both legacy big-endian and
|
|
102
|
+
* wire-v2 little-endian peers can parse the frame.
|
|
103
|
+
*/
|
|
104
|
+
public static byte[] packDataCommand(byte[] data, byte cmdType, K900LengthCodec.Endian endian) {
|
|
88
105
|
if (data == null) {
|
|
89
106
|
return null;
|
|
90
107
|
}
|
|
@@ -101,13 +118,8 @@ public class K900ProtocolUtils {
|
|
|
101
118
|
// Command type
|
|
102
119
|
result[2] = cmdType;
|
|
103
120
|
|
|
104
|
-
// Length (2 bytes,
|
|
105
|
-
result
|
|
106
|
-
result[4] = (byte)(dataLength & 0xFF); // LSB second
|
|
107
|
-
|
|
108
|
-
// Original little-endian implementation (commented out)
|
|
109
|
-
// result[3] = (byte)(dataLength & 0xFF); // LSB first
|
|
110
|
-
// result[4] = (byte)((dataLength >> 8) & 0xFF); // MSB second
|
|
121
|
+
// Length (2 bytes, negotiated endianness)
|
|
122
|
+
K900LengthCodec.writeLength(result, 3, dataLength, endian);
|
|
111
123
|
|
|
112
124
|
// Copy the data
|
|
113
125
|
System.arraycopy(data, 0, result, 5, dataLength);
|
|
@@ -129,34 +141,15 @@ public class K900ProtocolUtils {
|
|
|
129
141
|
* @return Byte array with packed data according to protocol format
|
|
130
142
|
*/
|
|
131
143
|
public static byte[] packDataToK900(byte[] data, byte cmdType) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
int dataLength = data.length;
|
|
137
|
-
|
|
138
|
-
// Command structure: ## + type + length(2 bytes) + data + $$
|
|
139
|
-
byte[] result = new byte[dataLength + 7]; // 2(start) + 1(type) + 2(length) + data + 2(end)
|
|
140
|
-
|
|
141
|
-
// Start code ##
|
|
142
|
-
result[0] = CMD_START_CODE[0]; // #
|
|
143
|
-
result[1] = CMD_START_CODE[1]; // #
|
|
144
|
-
|
|
145
|
-
// Command type
|
|
146
|
-
result[2] = cmdType;
|
|
147
|
-
|
|
148
|
-
// Length (2 bytes, little-endian for phone-to-device)
|
|
149
|
-
result[3] = (byte) (dataLength & 0xFF); // LSB first
|
|
150
|
-
result[4] = (byte) ((dataLength >> 8) & 0xFF); // MSB second
|
|
151
|
-
|
|
152
|
-
// Copy the data
|
|
153
|
-
System.arraycopy(data, 0, result, 5, dataLength);
|
|
154
|
-
|
|
155
|
-
// End code $$
|
|
156
|
-
result[5 + dataLength] = CMD_END_CODE[0]; // $
|
|
157
|
-
result[6 + dataLength] = CMD_END_CODE[1]; // $
|
|
144
|
+
return packDataToK900(data, cmdType, K900LengthCodec.Endian.BE);
|
|
145
|
+
}
|
|
158
146
|
|
|
159
|
-
|
|
147
|
+
/**
|
|
148
|
+
* Pack raw byte data with K900 BES2700 protocol format for phone-to-device communication.
|
|
149
|
+
* Format: ## + command_type + length(2bytes) + data + $$
|
|
150
|
+
*/
|
|
151
|
+
public static byte[] packDataToK900(byte[] data, byte cmdType, K900LengthCodec.Endian endian) {
|
|
152
|
+
return packDataCommand(data, cmdType, endian);
|
|
160
153
|
}
|
|
161
154
|
|
|
162
155
|
/**
|
|
@@ -168,6 +161,15 @@ public class K900ProtocolUtils {
|
|
|
168
161
|
* @return Byte array with packed data according to protocol format
|
|
169
162
|
*/
|
|
170
163
|
public static byte[] packJsonToK900(String jsonData, boolean wakeup) {
|
|
164
|
+
return packJsonToK900(jsonData, wakeup, K900LengthCodec.Endian.BE);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Pack a C-wrapped JSON string for phone-to-glasses transmission, writing the STRING frame
|
|
169
|
+
* length with the negotiated per-link endianness. Legacy big-endian glasses require BE until
|
|
170
|
+
* they advertise {@code wire_caps.k900_le}.
|
|
171
|
+
*/
|
|
172
|
+
public static byte[] packJsonToK900(String jsonData, boolean wakeup, K900LengthCodec.Endian endian) {
|
|
171
173
|
if (jsonData == null) {
|
|
172
174
|
return null;
|
|
173
175
|
}
|
|
@@ -183,9 +185,9 @@ public class K900ProtocolUtils {
|
|
|
183
185
|
// Convert to string
|
|
184
186
|
String wrappedJson = wrapper.toString();
|
|
185
187
|
|
|
186
|
-
// Then pack with BES2700 protocol format using
|
|
188
|
+
// Then pack with BES2700 protocol format using the negotiated endianness
|
|
187
189
|
byte[] jsonBytes = wrappedJson.getBytes(StandardCharsets.UTF_8);
|
|
188
|
-
return
|
|
190
|
+
return packDataCommand(jsonBytes, CMD_TYPE_STRING, endian);
|
|
189
191
|
|
|
190
192
|
} catch (JSONException e) {
|
|
191
193
|
android.util.Log.e("K900ProtocolUtils", "Error creating JSON wrapper for K900", e);
|
|
@@ -203,6 +205,14 @@ public class K900ProtocolUtils {
|
|
|
203
205
|
* @return Formatted bytes ready for transmission
|
|
204
206
|
*/
|
|
205
207
|
public static byte[] formatMessageForTransmission(String jsonData) {
|
|
208
|
+
return formatMessageForTransmission(jsonData, K900LengthCodec.Endian.BE);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Format an ASG-client JSON message for transmission, writing the STRING frame length with the
|
|
213
|
+
* negotiated per-link endianness.
|
|
214
|
+
*/
|
|
215
|
+
public static byte[] formatMessageForTransmission(String jsonData, K900LengthCodec.Endian endian) {
|
|
206
216
|
try {
|
|
207
217
|
android.util.Log.e("K900ProtocolUtils", "🔄 Formatting message: " + jsonData);
|
|
208
218
|
|
|
@@ -218,7 +228,9 @@ public class K900ProtocolUtils {
|
|
|
218
228
|
android.util.Log.e("K900ProtocolUtils", "🔄 After C-wrapping: " + wrappedJson);
|
|
219
229
|
|
|
220
230
|
// Now format with BES2700 protocol
|
|
221
|
-
byte[] result =
|
|
231
|
+
byte[] result =
|
|
232
|
+
packDataCommand(
|
|
233
|
+
wrappedJson.getBytes(StandardCharsets.UTF_8), CMD_TYPE_STRING, endian);
|
|
222
234
|
|
|
223
235
|
// Log some bytes for debugging
|
|
224
236
|
StringBuilder hexDump = new StringBuilder();
|
|
@@ -233,7 +245,7 @@ public class K900ProtocolUtils {
|
|
|
233
245
|
} catch (JSONException e) {
|
|
234
246
|
android.util.Log.e("K900ProtocolUtils", "❌ Error in formatMessageForTransmission", e);
|
|
235
247
|
// Fallback: if json is invalid, still try to pack it without validation
|
|
236
|
-
return packJsonCommand(jsonData);
|
|
248
|
+
return packJsonCommand(jsonData, endian);
|
|
237
249
|
}
|
|
238
250
|
}
|
|
239
251
|
|
|
@@ -264,9 +276,6 @@ public class K900ProtocolUtils {
|
|
|
264
276
|
return false;
|
|
265
277
|
}
|
|
266
278
|
|
|
267
|
-
Log.d("K900ProtocolUtils", "isK900ProtocolFormat: " + data[0] + " " + data[1]);
|
|
268
|
-
Log.d("K900ProtocolUtils", "CMD_START_CODE: " + CMD_START_CODE[0] + " " + CMD_START_CODE[1]);
|
|
269
|
-
|
|
270
279
|
return data[0] == CMD_START_CODE[0] &&
|
|
271
280
|
data[1] == CMD_START_CODE[1];
|
|
272
281
|
}
|
|
@@ -304,56 +313,59 @@ public class K900ProtocolUtils {
|
|
|
304
313
|
* @return Raw payload data or null if format is invalid
|
|
305
314
|
*/
|
|
306
315
|
public static byte[] extractPayload(byte[] protocolData) {
|
|
316
|
+
return extractPayload(protocolData, K900LengthCodec.Endian.BE);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Extract payload from a K900 STRING frame, reading the length field with an explicit
|
|
321
|
+
* endianness. Prefer {@link #extractPayloadAuto} on receive when the peer endianness is not yet
|
|
322
|
+
* negotiated.
|
|
323
|
+
*/
|
|
324
|
+
public static byte[] extractPayload(byte[] protocolData, K900LengthCodec.Endian endian) {
|
|
307
325
|
if (!isK900ProtocolFormat(protocolData) || protocolData.length < 7) {
|
|
308
326
|
return null;
|
|
309
327
|
}
|
|
310
328
|
|
|
311
|
-
|
|
312
|
-
int length = ((protocolData[3] & 0xFF) << 8) | (protocolData[4] & 0xFF);
|
|
313
|
-
|
|
314
|
-
// Original little-endian implementation (commented out)
|
|
315
|
-
// int length = (protocolData[3] & 0xFF) | ((protocolData[4] & 0xFF) << 8);
|
|
329
|
+
int length = K900LengthCodec.readLength(protocolData, 3, endian);
|
|
316
330
|
|
|
317
331
|
if (length + 7 > protocolData.length) {
|
|
318
332
|
return null; // Invalid length
|
|
319
333
|
}
|
|
320
334
|
|
|
321
|
-
// Extract payload
|
|
322
335
|
byte[] payload = new byte[length];
|
|
323
336
|
System.arraycopy(protocolData, 5, payload, 0, length);
|
|
324
337
|
return payload;
|
|
325
338
|
}
|
|
326
339
|
|
|
327
340
|
/**
|
|
328
|
-
* Extract payload from K900
|
|
329
|
-
*
|
|
330
|
-
*
|
|
341
|
+
* Extract payload from a K900 STRING frame, heuristically detecting the length field's
|
|
342
|
+
* endianness. Safe RX entry point when the peer endianness is not yet negotiated (fixes the
|
|
343
|
+
* "Extracted length=9472" misframe against legacy big-endian BES firmware).
|
|
331
344
|
*/
|
|
332
|
-
public static byte[]
|
|
345
|
+
public static byte[] extractPayloadAuto(byte[] protocolData) {
|
|
333
346
|
if (!isK900ProtocolFormat(protocolData) || protocolData.length < 7) {
|
|
334
|
-
Log.e("K900ProtocolUtils", "extractPayloadFromK900: Not K900 format or too short. Length=" +
|
|
335
|
-
(protocolData != null ? protocolData.length : 0));
|
|
336
347
|
return null;
|
|
337
348
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
Log.d("K900ProtocolUtils", "extractPayloadFromK900: Extracted length=" + length +
|
|
343
|
-
", message length=" + protocolData.length + ", expected total=" + (length + 7));
|
|
344
|
-
|
|
345
|
-
if (length + 7 > protocolData.length) {
|
|
346
|
-
Log.e("K900ProtocolUtils", "extractPayloadFromK900: Invalid length. Need " +
|
|
347
|
-
(length + 7) + " bytes but have " + protocolData.length);
|
|
348
|
-
return null; // Invalid length
|
|
349
|
+
K900LengthCodec.Detected detected = K900LengthCodec.detectLength(protocolData);
|
|
350
|
+
if (detected == null) {
|
|
351
|
+
return null;
|
|
349
352
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
byte[] payload = new byte[length];
|
|
353
|
-
System.arraycopy(protocolData, 5, payload, 0, length);
|
|
353
|
+
byte[] payload = new byte[detected.length];
|
|
354
|
+
System.arraycopy(protocolData, 5, payload, 0, detected.length);
|
|
354
355
|
return payload;
|
|
355
356
|
}
|
|
356
357
|
|
|
358
|
+
/**
|
|
359
|
+
* Extract payload from K900 protocol formatted data received from device
|
|
360
|
+
* Uses little-endian byte order for length field
|
|
361
|
+
* @return Raw payload data or null if format is invalid
|
|
362
|
+
*/
|
|
363
|
+
public static byte[] extractPayloadFromK900(byte[] protocolData) {
|
|
364
|
+
// Retained for backward compatibility: device-to-phone frames may arrive in either
|
|
365
|
+
// endianness depending on firmware vintage, so auto-detect rather than assuming LE.
|
|
366
|
+
return extractPayloadAuto(protocolData);
|
|
367
|
+
}
|
|
368
|
+
|
|
357
369
|
/**
|
|
358
370
|
* Process received bytes from Bluetooth into a JSON object
|
|
359
371
|
* Handles K900 protocol format detection, payload extraction, and C-field unwrapping
|
|
@@ -379,8 +391,13 @@ public class K900ProtocolUtils {
|
|
|
379
391
|
// Extract the command type
|
|
380
392
|
byte commandType = data[2];
|
|
381
393
|
|
|
382
|
-
// Extract the length
|
|
383
|
-
|
|
394
|
+
// Extract the length, auto-detecting endianness so we parse both legacy big-endian and
|
|
395
|
+
// wire-v2 little-endian frames.
|
|
396
|
+
K900LengthCodec.Detected detected = K900LengthCodec.detectLength(data);
|
|
397
|
+
int payloadLength =
|
|
398
|
+
detected != null
|
|
399
|
+
? detected.length
|
|
400
|
+
: ((data[3] & 0xFF) | ((data[4] & 0xFF) << 8));
|
|
384
401
|
|
|
385
402
|
android.util.Log.d("K900ProtocolUtils", "Command type: 0x" + String.format("%02X", commandType) +
|
|
386
403
|
", Payload length: " + payloadLength);
|
|
@@ -565,6 +582,23 @@ public class K900ProtocolUtils {
|
|
|
565
582
|
return false;
|
|
566
583
|
}
|
|
567
584
|
|
|
585
|
+
public static boolean isBinaryFrame(byte[] data) {
|
|
586
|
+
return BleWireProtocol.isBinaryFrame(data);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
public static byte[] packBinaryFragment(
|
|
590
|
+
byte flags, int msgId, int fragIdx, int fragCount, byte[] payload) {
|
|
591
|
+
return BleWireProtocol.packBinaryFragment(flags, msgId, fragIdx, fragCount, payload);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
public static BleWireProtocol.BinaryFragmentInfo extractBinaryFragmentInfo(byte[] frame) {
|
|
595
|
+
return BleWireProtocol.extractBinaryFragmentInfo(frame);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
public static byte[] extractBinaryPayload(byte[] frame) {
|
|
599
|
+
return BleWireProtocol.extractBinaryPayload(frame);
|
|
600
|
+
}
|
|
601
|
+
|
|
568
602
|
/**
|
|
569
603
|
* Inner class to represent file packet information
|
|
570
604
|
*/
|
|
@@ -757,7 +791,9 @@ public class K900ProtocolUtils {
|
|
|
757
791
|
Log.e("K900ProtocolUtils", "File packet checksum failed. Expected: " +
|
|
758
792
|
String.format("%02X", info.verifyCode) + ", Calculated: " +
|
|
759
793
|
String.format("%02X", calculatedVerify));
|
|
760
|
-
} else
|
|
794
|
+
} else if (info.packIndex == 0
|
|
795
|
+
|| info.packIndex % 32 == 0
|
|
796
|
+
|| info.packIndex == (info.fileSize + FILE_PACK_SIZE - 1) / FILE_PACK_SIZE - 1) {
|
|
761
797
|
Log.d("K900ProtocolUtils", "File packet extracted successfully: index=" + info.packIndex +
|
|
762
798
|
", size=" + info.packSize + ", fileName=" + info.fileName);
|
|
763
799
|
}
|