From ec340f0b5e1ac62b1d0691a4c37dab7ad56990a9 Mon Sep 17 00:00:00 2001 From: cayden Date: Thu, 14 Nov 2024 18:03:32 -0500 Subject: [PATCH 01/11] start adding g1 custom, probably will pull back to use official SDK --- .../SmartGlassesAndroidService.java | 3 +- .../SmartGlassesRepresentative.java | 4 + .../hci/MicrophoneLocalAndBluetooth.java | 7 +- .../EvenRealitiesG1SGC.java | 408 ++++++++++++++++++ .../supportedglasses/EvenRealitiesG1.java | 18 + .../SmartGlassesOperatingSystem.java | 1 + 6 files changed, 438 insertions(+), 3 deletions(-) create mode 100644 SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java create mode 100644 SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/supportedglasses/EvenRealitiesG1.java diff --git a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/SmartGlassesAndroidService.java b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/SmartGlassesAndroidService.java index 58e0373d..9d987444 100644 --- a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/SmartGlassesAndroidService.java +++ b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/SmartGlassesAndroidService.java @@ -41,6 +41,7 @@ import com.teamopensmartglasses.smartglassesmanager.speechrecognition.ASR_FRAMEWORKS; import com.teamopensmartglasses.smartglassesmanager.speechrecognition.SpeechRecSwitchSystem; import com.teamopensmartglasses.smartglassesmanager.supportedglasses.AudioWearable; +import com.teamopensmartglasses.smartglassesmanager.supportedglasses.EvenRealitiesG1; import com.teamopensmartglasses.smartglassesmanager.supportedglasses.InmoAirOne; import com.teamopensmartglasses.smartglassesmanager.supportedglasses.SmartGlassesDevice; import com.teamopensmartglasses.smartglassesmanager.supportedglasses.SmartGlassesOperatingSystem; @@ -469,7 +470,7 @@ public void aioConnectSmartGlasses(){ } String preferred = getPreferredWearable(this.getApplicationContext()); - smartGlassesDevices = new ArrayList(Arrays.asList(new VuzixUltralite(), new VuzixShield(), new InmoAirOne(), new TCLRayNeoXTwo())); + smartGlassesDevices = new ArrayList(Arrays.asList(new VuzixUltralite(), new EvenRealitiesG1(), new VuzixShield(), new InmoAirOne(), new TCLRayNeoXTwo())); for (int i = 0; i < smartGlassesDevices.size(); i++){ if (smartGlassesDevices.get(i).deviceModelName.equals(preferred)){ // Move to start for earliest search priority diff --git a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/SmartGlassesRepresentative.java b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/SmartGlassesRepresentative.java index 595ffc95..4064df0d 100644 --- a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/SmartGlassesRepresentative.java +++ b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/SmartGlassesRepresentative.java @@ -20,6 +20,7 @@ import com.teamopensmartglasses.smartglassesmanager.eventbusmessages.SetFontSizeEvent; import com.teamopensmartglasses.smartglassesmanager.eventbusmessages.TextWallViewRequestEvent; import com.teamopensmartglasses.smartglassesmanager.smartglassescommunicators.AudioWearableSGC; +import com.teamopensmartglasses.smartglassesmanager.smartglassescommunicators.EvenRealitiesG1SGC; import com.teamopensmartglasses.smartglassesmanager.smartglassescommunicators.UltraliteSGC; import com.teamopensmartglasses.smartglassesmanager.eventbusmessages.BulletPointListViewRequestEvent; import com.teamopensmartglasses.smartglassesmanager.eventbusmessages.CenteredTextViewRequestEvent; @@ -94,6 +95,9 @@ public void connectToSmartGlasses(){ case ULTRALITE_MCU_OS_GLASSES: smartGlassesCommunicator = new UltraliteSGC(context, lifecycleOwner); break; + case EVEN_REALITIES_G1_MCU_OS_GLASSES: + smartGlassesCommunicator = new EvenRealitiesG1SGC(context); + break; } smartGlassesCommunicator.connectToSmartGlasses(); diff --git a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/hci/MicrophoneLocalAndBluetooth.java b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/hci/MicrophoneLocalAndBluetooth.java index 36c7f979..0f1ef9e9 100644 --- a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/hci/MicrophoneLocalAndBluetooth.java +++ b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/hci/MicrophoneLocalAndBluetooth.java @@ -118,7 +118,6 @@ private void handleDisconnectBluetoothDevice() { public MicrophoneLocalAndBluetooth(Context context, boolean useBluetoothSco, AudioChunkCallback chunkCallback) { this(context, chunkCallback); - this.shouldUseHearItBleMicrophone = true; useBluetoothMic(useBluetoothSco); } @@ -286,7 +285,11 @@ public void run() { } b_buffer.order(ByteOrder.LITTLE_ENDIAN); b_buffer.asShortBuffer().put(short_buffer); - if (hearItBleMicrophone != null && !hearItBleMicrophone.isConnected()) { + if (shouldUseHearItBleMicrophone) { + if (hearItBleMicrophone != null && !hearItBleMicrophone.isConnected()) { + mChunkCallback.onSuccess(b_buffer); + } + } else { mChunkCallback.onSuccess(b_buffer); } b_buffer.clear(); diff --git a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java new file mode 100644 index 00000000..67958a20 --- /dev/null +++ b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java @@ -0,0 +1,408 @@ +package com.teamopensmartglasses.smartglassesmanager.smartglassescommunicators; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattService; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.graphics.Bitmap; +import android.os.Handler; +import android.util.Log; +import java.util.UUID; +import java.nio.ByteBuffer; +import java.util.concurrent.Semaphore; + +public class EvenRealitiesG1SGC extends SmartGlassesCommunicator { + private static final String TAG = "WearableAi_EvenRealitiesG1SGC"; + + // BLE UUIDs from the protocol + private static final UUID UART_SERVICE_UUID = UUID.fromString("6E400001-B5A3-F393-E0A9-E50E24DCCA9E"); + private static final UUID UART_TX_CHAR_UUID = UUID.fromString("6E400002-B5A3-F393-E0A9-E50E24DCCA9E"); + private static final UUID UART_RX_CHAR_UUID = UUID.fromString("6E400003-B5A3-F393-E0A9-E50E24DCCA9E"); + + private Context context; + private BluetoothGatt leftGlassGatt; + private BluetoothGatt rightGlassGatt; + private BluetoothGattCharacteristic leftTxChar; + private BluetoothGattCharacteristic rightTxChar; + private final Handler handler = new Handler(); + private final Semaphore sendSemaphore = new Semaphore(1); + private boolean isLeftConnected = false; + private boolean isRightConnected = false; + private int currentSeq = 0; + private static final int DEFAULT_LINGER_TIME = 15; + + public EvenRealitiesG1SGC(Context context) { + super(); + this.context = context; + mConnectState = 0; + } + + private final BluetoothGattCallback rightGattCallback = new BluetoothGattCallback() { + @Override + public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { + if (newState == BluetoothProfile.STATE_CONNECTED) { + Log.d(TAG, "Right glass connected, discovering services..."); + gatt.discoverServices(); + isRightConnected = true; + updateConnectionState(); + } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { + isRightConnected = false; + updateConnectionState(); + } + } + + @Override + public void onServicesDiscovered(BluetoothGatt gatt, int status) { + if (status == BluetoothGatt.GATT_SUCCESS) { + BluetoothGattService uartService = gatt.getService(UART_SERVICE_UUID); + if (uartService != null) { + rightTxChar = uartService.getCharacteristic(UART_TX_CHAR_UUID); + } + } + } + + @Override + public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + if (status == BluetoothGatt.GATT_SUCCESS) { + Log.d(TAG, "Right glass write successful"); + } + } + }; + + private void updateConnectionState() { + if (isLeftConnected && isRightConnected) { + mConnectState = 2; + connectionEvent(2); + } else if (isLeftConnected || isRightConnected) { + mConnectState = 1; + connectionEvent(1); + } else { + mConnectState = 0; + connectionEvent(0); + } + } + + // Add this as a class field + private BluetoothAdapter.LeScanCallback leScanCallback; + + @Override + public void connectToSmartGlasses() { + BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + + // Create the callback + leScanCallback = (device, rssi, scanRecord) -> { + String name = device.getName(); + if (name != null && name.contains("G1")) { + if (name.contains("_L_") && leftGlassGatt == null) { + leftGlassGatt = device.connectGatt(context, false, leftGattCallback); + } else if (name.contains("_R_") && rightGlassGatt == null) { + rightGlassGatt = device.connectGatt(context, false, rightGattCallback); + } + } + }; + + // Start scanning with the callback + bluetoothAdapter.startLeScan(leScanCallback); + + // Stop scanning after 10 seconds + handler.postDelayed(() -> bluetoothAdapter.stopLeScan(leScanCallback), 10000); + } + +// private byte[] createTextPackage(String text, int currentPage, int totalPages) { +// byte[] textBytes = text.getBytes(); +// int totalPackages = (textBytes.length + 180) / 181; // Max 181 bytes per package +// +// ByteBuffer buffer = ByteBuffer.allocate(9 + textBytes.length); +// buffer.put((byte)0x4E); // Command +// buffer.put((byte)(currentSeq++ & 0xFF)); // Sequence number +// buffer.put((byte)totalPackages); // Total packages +// buffer.put((byte)0); // Current package +// buffer.put((byte)0x31); // Screen status (0x30 | 0x01) +// buffer.put((byte)0); // new_char_pos0 +// buffer.put((byte)0); // new_char_pos1 +// buffer.put((byte)currentPage); // Current page +// buffer.put((byte)totalPages); // Max pages +// buffer.put(textBytes); // Text data +// +// return buffer.array(); +// } + + public boolean stopper = false; +// private void sendDataSequentially(byte[] data) { +// if (stopper){ +// return; +// } +// stopper = true; +// Log.d(TAG, "Sending data to G1..."); +// try { +// // First send to left glass +// sendSemaphore.acquire(); +// if (leftGlassGatt != null && leftTxChar != null) { +// leftTxChar.setValue(data); +// leftGlassGatt.writeCharacteristic(leftTxChar); +// } +// +// // Wait for left glass acknowledgment before sending to right +// sendSemaphore.acquire(); +// if (rightGlassGatt != null && rightTxChar != null) { +// rightTxChar.setValue(data); +// rightGlassGatt.writeCharacteristic(rightTxChar); +// } +// } catch (InterruptedException e) { +// Log.e(TAG, "Interrupted while sending data", e); +// } +// } + + @Override + public void displayReferenceCardSimple(String title, String body) { + displayReferenceCardSimple(title, body, DEFAULT_LINGER_TIME); + } + +// public void displayReferenceCardSimple(String title, String body, int lingerTime) { +// if (!isConnected()) { +// Log.d(TAG, "Not connected to glasses"); +// return; +// } +// +// String displayText = title.isEmpty() ? body : title + "\n" + body; +// byte[] data = createTextPackage(displayText, 1, 1); +// sendDataSequentially(data); +// } + + public void displayReferenceCardSimple(String title, String body, int lingerTime) { + if (!isConnected()) { + Log.d(TAG, "Not connected to glasses"); + return; + } + + // Format text + String displayText = title.isEmpty() ? body : title + "\n" + body; + + // Create and send package + byte[] data = createTextPackage(displayText, 1, 1); + Log.d(TAG, "Sending package: " + bytesToHex(data)); + sendDataSequentially(data); + } + + + + + + + + ///////newwwwwwwwwwwwwwwwww + // Add this at class level + private static final long WRITE_TIMEOUT_MS = 1000; + private static final long DELAY_BETWEEN_SENDS_MS = 400; // 0.4 seconds delay between sends + + private byte[] createTextPackage(String text, int currentPage, int totalPages) { + byte[] textBytes = text.getBytes(); + Log.d(TAG, "Creating text package for text of length: " + textBytes.length); + + ByteBuffer buffer = ByteBuffer.allocate(9 + textBytes.length); + buffer.put((byte)0x4E); // Command + buffer.put((byte)(currentSeq++ & 0xFF)); // Sequence number + buffer.put((byte)1); // Total packages (just sending as 1 for now) + buffer.put((byte)0); // Current package +// buffer.put((byte)0x31); // Screen status (0x30 | 0x01) + buffer.put((byte)0x51); // Only display new content without Even AI + buffer.put((byte)0); // new_char_pos0 + buffer.put((byte)0); // new_char_pos1 + buffer.put((byte)currentPage); // Current page number + buffer.put((byte)totalPages); // Total pages + buffer.put(textBytes); // Text data + + byte[] result = buffer.array(); + Log.d(TAG, "Created package: " + bytesToHex(result)); + return result; + } + + private void sendDataSequentially(byte[] data) { + if (stopper) { + return; + } + stopper = true; + + new Thread(() -> { + try { + if (leftGlassGatt != null && leftTxChar != null) { + leftTxChar.setValue(data); + if (!leftGlassGatt.writeCharacteristic(leftTxChar)) { + stopper = false; + return; + } + Thread.sleep(DELAY_BETWEEN_SENDS_MS); + } + + if (rightGlassGatt != null && rightTxChar != null) { + rightTxChar.setValue(data); + if (!rightGlassGatt.writeCharacteristic(rightTxChar)) { + stopper = false; + return; + } + } + Thread.sleep(DELAY_BETWEEN_SENDS_MS); + + byte[] completeData = data.clone(); + completeData[4] = 0x40; + + if (leftGlassGatt != null && leftTxChar != null) { + leftTxChar.setValue(completeData); + leftGlassGatt.writeCharacteristic(leftTxChar); + Thread.sleep(DELAY_BETWEEN_SENDS_MS); + } + + if (rightGlassGatt != null && rightTxChar != null) { + rightTxChar.setValue(completeData); + rightGlassGatt.writeCharacteristic(rightTxChar); + } + + Thread.sleep(DELAY_BETWEEN_SENDS_MS); + stopper = false; + + } catch (Exception e) { + Log.e(TAG, "Error sending data: " + e.getMessage()); + stopper = false; + } + }).start(); + } + + // Updated callbacks to include better logging + private final BluetoothGattCallback leftGattCallback = new BluetoothGattCallback() { + @Override + public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { + Log.d(TAG, "Left glass connection state change: status=" + status + " newState=" + newState); + if (newState == BluetoothProfile.STATE_CONNECTED) { + Log.d(TAG, "Left glass connected, discovering services..."); + gatt.discoverServices(); + isLeftConnected = true; + updateConnectionState(); + } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { + isLeftConnected = false; + updateConnectionState(); + Log.d(TAG, "Left glass disconnected"); + } + } + + @Override + public void onServicesDiscovered(BluetoothGatt gatt, int status) { + Log.d(TAG, "Left glass services discovered: status=" + status); + if (status == BluetoothGatt.GATT_SUCCESS) { + BluetoothGattService uartService = gatt.getService(UART_SERVICE_UUID); + if (uartService != null) { + leftTxChar = uartService.getCharacteristic(UART_TX_CHAR_UUID); + if (leftTxChar != null) { + Log.d(TAG, "Left glass TX characteristic found"); + } else { + Log.e(TAG, "Left glass TX characteristic not found"); + } + } else { + Log.e(TAG, "Left glass UART service not found"); + } + } + } + + @Override + public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + Log.d(TAG, "Left glass characteristic write: status=" + status); + if (status == BluetoothGatt.GATT_SUCCESS) { + Log.d(TAG, "Left glass write successful"); + } else { + Log.e(TAG, "Left glass write failed"); + } + } + }; + + // Similar updates for rightGattCallback... + private static String bytesToHex(byte[] bytes) { + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02X ", b)); + } + return sb.toString(); + } + + @Override + public void displayTextWall(String text) { + if (!isConnected()) { + Log.d(TAG, "Not connected to glasses"); + return; + } + + byte[] data = createTextPackage(text, 1, 1); + sendDataSequentially(data); + } + + @Override + protected void setFontSizes() { + // Empty implementation + } + + @Override + public void destroy() { + if (leftGlassGatt != null) { + leftGlassGatt.disconnect(); + leftGlassGatt.close(); + } + if (rightGlassGatt != null) { + rightGlassGatt.disconnect(); + rightGlassGatt.close(); + } + } + + @Override + public boolean isConnected() { + return mConnectState == 2; + } + + // Empty implementations of other required methods + public void displayCenteredText(String text) {} + public void showNaturalLanguageCommandScreen(String prompt, String naturalLanguageInput) {} + public void updateNaturalLanguageCommandScreen(String naturalLanguageArgs) {} + + @Override + public void scrollingTextViewIntermediateText(String text) { + + } + + @Override + public void scrollingTextViewFinalText(String text) { + + } + + @Override + public void stopScrollingTextViewMode() { + + } + + @Override + public void displayPromptView(String title, String[] options) { + + } + + @Override + public void displayTextLine(String text) { + + } + + @Override + public void displayBitmap(Bitmap bmp) { + + } + + public void blankScreen() {} + public void displayDoubleTextWall(String textTop, String textBottom) {} + public void showHomeScreen() {} + + @Override + public void setFontSize(SmartGlassesFontSize fontSize) { + + } + + public void displayRowsCard(String[] rowStrings) {} + public void displayBulletList(String title, String[] bullets) {} + public void displayReferenceCardImage(String title, String body, String imgUrl) {} +} \ No newline at end of file diff --git a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/supportedglasses/EvenRealitiesG1.java b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/supportedglasses/EvenRealitiesG1.java new file mode 100644 index 00000000..9f5734e2 --- /dev/null +++ b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/supportedglasses/EvenRealitiesG1.java @@ -0,0 +1,18 @@ +package com.teamopensmartglasses.smartglassesmanager.supportedglasses; + +public class EvenRealitiesG1 extends SmartGlassesDevice { + public EvenRealitiesG1() { + deviceModelName = "Even Realities G1"; + deviceIconName = "er_g1"; + anySupport = true; + fullSupport = true; + glassesOs = SmartGlassesOperatingSystem.EVEN_REALITIES_G1_MCU_OS_GLASSES; + hasDisplay = true; + hasSpeakers = false; + hasCamera = false; + hasInMic = false; + hasOutMic = false; + useScoMic = true; + weight = 37; + } +} diff --git a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/supportedglasses/SmartGlassesOperatingSystem.java b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/supportedglasses/SmartGlassesOperatingSystem.java index 9a682470..99cdc1e1 100644 --- a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/supportedglasses/SmartGlassesOperatingSystem.java +++ b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/supportedglasses/SmartGlassesOperatingSystem.java @@ -4,5 +4,6 @@ public enum SmartGlassesOperatingSystem { ANDROID_OS_GLASSES, ACTIVELOOK_OS_GLASSES, ULTRALITE_MCU_OS_GLASSES, + EVEN_REALITIES_G1_MCU_OS_GLASSES, AUDIO_WEARABLE_GLASSES } From 7100ce5ae71d3d1336a8e929257442f9480fa39b Mon Sep 17 00:00:00 2001 From: cayden Date: Fri, 15 Nov 2024 16:27:02 -0500 Subject: [PATCH 02/11] g1 almost working - heartbeat, text displaying, but get Even AI error --- .../EvenRealitiesG1SGC.java | 471 ++++++++---------- 1 file changed, 215 insertions(+), 256 deletions(-) diff --git a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java index 67958a20..d117b008 100644 --- a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java +++ b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java @@ -4,35 +4,41 @@ import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.graphics.Bitmap; import android.os.Handler; import android.util.Log; -import java.util.UUID; + import java.nio.ByteBuffer; +import java.util.UUID; import java.util.concurrent.Semaphore; public class EvenRealitiesG1SGC extends SmartGlassesCommunicator { private static final String TAG = "WearableAi_EvenRealitiesG1SGC"; - // BLE UUIDs from the protocol private static final UUID UART_SERVICE_UUID = UUID.fromString("6E400001-B5A3-F393-E0A9-E50E24DCCA9E"); private static final UUID UART_TX_CHAR_UUID = UUID.fromString("6E400002-B5A3-F393-E0A9-E50E24DCCA9E"); private static final UUID UART_RX_CHAR_UUID = UUID.fromString("6E400003-B5A3-F393-E0A9-E50E24DCCA9E"); + private static final UUID CLIENT_CHARACTERISTIC_CONFIG_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); private Context context; private BluetoothGatt leftGlassGatt; private BluetoothGatt rightGlassGatt; private BluetoothGattCharacteristic leftTxChar; private BluetoothGattCharacteristic rightTxChar; + private BluetoothGattCharacteristic leftRxChar; + private BluetoothGattCharacteristic rightRxChar; private final Handler handler = new Handler(); private final Semaphore sendSemaphore = new Semaphore(1); private boolean isLeftConnected = false; private boolean isRightConnected = false; private int currentSeq = 0; - private static final int DEFAULT_LINGER_TIME = 15; + private boolean stopper = false; + + private static final long DELAY_BETWEEN_SENDS_MS = 400; public EvenRealitiesG1SGC(Context context) { super(); @@ -40,37 +46,88 @@ public EvenRealitiesG1SGC(Context context) { mConnectState = 0; } - private final BluetoothGattCallback rightGattCallback = new BluetoothGattCallback() { - @Override - public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { - if (newState == BluetoothProfile.STATE_CONNECTED) { - Log.d(TAG, "Right glass connected, discovering services..."); - gatt.discoverServices(); - isRightConnected = true; - updateConnectionState(); - } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { - isRightConnected = false; - updateConnectionState(); + private final BluetoothGattCallback leftGattCallback = createGattCallback("Left"); + private final BluetoothGattCallback rightGattCallback = createGattCallback("Right"); + + private BluetoothGattCallback createGattCallback(String side) { + return new BluetoothGattCallback() { + @Override + public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { + if (newState == BluetoothProfile.STATE_CONNECTED) { + Log.d(TAG, side + " glass connected, discovering services..."); + gatt.discoverServices(); + if ("Left".equals(side)) isLeftConnected = true; + else isRightConnected = true; + updateConnectionState(); + } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { + if ("Left".equals(side)) isLeftConnected = false; + else isRightConnected = false; + updateConnectionState(); + Log.d(TAG, side + " glass disconnected"); + } } - } - @Override - public void onServicesDiscovered(BluetoothGatt gatt, int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { - BluetoothGattService uartService = gatt.getService(UART_SERVICE_UUID); - if (uartService != null) { - rightTxChar = uartService.getCharacteristic(UART_TX_CHAR_UUID); + @Override + public void onServicesDiscovered(BluetoothGatt gatt, int status) { + if (status == BluetoothGatt.GATT_SUCCESS) { + BluetoothGattService uartService = gatt.getService(UART_SERVICE_UUID); + if (uartService != null) { + BluetoothGattCharacteristic txChar = uartService.getCharacteristic(UART_TX_CHAR_UUID); + BluetoothGattCharacteristic rxChar = uartService.getCharacteristic(UART_RX_CHAR_UUID); + + if (txChar != null) { + if ("Left".equals(side)) leftTxChar = txChar; + else rightTxChar = txChar; + Log.d(TAG, side + " glass TX characteristic found"); + } + + if (rxChar != null) { + if ("Left".equals(side)) leftRxChar = rxChar; + else rightRxChar = rxChar; + enableNotification(gatt, rxChar); + Log.d(TAG, side + " glass RX characteristic found"); + } + + startHeartbeat(); + } else { + Log.e(TAG, side + " glass UART service not found"); + } } } - } - @Override - public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { - Log.d(TAG, "Right glass write successful"); + @Override + public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + if (status == BluetoothGatt.GATT_SUCCESS) { + Log.d(TAG, side + " glass write successful"); + } else { + Log.e(TAG, side + " glass write failed with status: " + status); + } } + + @Override + public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + if (characteristic.getUuid().equals(UART_RX_CHAR_UUID)) { + byte[] data = characteristic.getValue(); + Log.d(TAG, "Received response: " + bytesToHex(data)); + + // Check if it's a heartbeat response + if (data.length > 0 && data[0] == 0x25) { // Check for heartbeat response + Log.d(TAG, "Heartbeat response received"); + } + } + } + }; + } + + + private void enableNotification(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + gatt.setCharacteristicNotification(characteristic, true); + BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_UUID); + if (descriptor != null) { + descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); + gatt.writeDescriptor(descriptor); } - }; + } private void updateConnectionState() { if (isLeftConnected && isRightConnected) { @@ -85,15 +142,11 @@ private void updateConnectionState() { } } - // Add this as a class field - private BluetoothAdapter.LeScanCallback leScanCallback; - @Override public void connectToSmartGlasses() { BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - // Create the callback - leScanCallback = (device, rssi, scanRecord) -> { + BluetoothAdapter.LeScanCallback leScanCallback = (device, rssi, scanRecord) -> { String name = device.getName(); if (name != null && name.contains("G1")) { if (name.contains("_L_") && leftGlassGatt == null) { @@ -104,243 +157,61 @@ public void connectToSmartGlasses() { } }; - // Start scanning with the callback bluetoothAdapter.startLeScan(leScanCallback); - - // Stop scanning after 10 seconds handler.postDelayed(() -> bluetoothAdapter.stopLeScan(leScanCallback), 10000); } -// private byte[] createTextPackage(String text, int currentPage, int totalPages) { -// byte[] textBytes = text.getBytes(); -// int totalPackages = (textBytes.length + 180) / 181; // Max 181 bytes per package -// -// ByteBuffer buffer = ByteBuffer.allocate(9 + textBytes.length); -// buffer.put((byte)0x4E); // Command -// buffer.put((byte)(currentSeq++ & 0xFF)); // Sequence number -// buffer.put((byte)totalPackages); // Total packages -// buffer.put((byte)0); // Current package -// buffer.put((byte)0x31); // Screen status (0x30 | 0x01) -// buffer.put((byte)0); // new_char_pos0 -// buffer.put((byte)0); // new_char_pos1 -// buffer.put((byte)currentPage); // Current page -// buffer.put((byte)totalPages); // Max pages -// buffer.put(textBytes); // Text data -// -// return buffer.array(); -// } - - public boolean stopper = false; -// private void sendDataSequentially(byte[] data) { -// if (stopper){ -// return; -// } -// stopper = true; -// Log.d(TAG, "Sending data to G1..."); -// try { -// // First send to left glass -// sendSemaphore.acquire(); -// if (leftGlassGatt != null && leftTxChar != null) { -// leftTxChar.setValue(data); -// leftGlassGatt.writeCharacteristic(leftTxChar); -// } -// -// // Wait for left glass acknowledgment before sending to right -// sendSemaphore.acquire(); -// if (rightGlassGatt != null && rightTxChar != null) { -// rightTxChar.setValue(data); -// rightGlassGatt.writeCharacteristic(rightTxChar); -// } -// } catch (InterruptedException e) { -// Log.e(TAG, "Interrupted while sending data", e); -// } -// } - - @Override - public void displayReferenceCardSimple(String title, String body) { - displayReferenceCardSimple(title, body, DEFAULT_LINGER_TIME); - } - -// public void displayReferenceCardSimple(String title, String body, int lingerTime) { -// if (!isConnected()) { -// Log.d(TAG, "Not connected to glasses"); -// return; -// } -// -// String displayText = title.isEmpty() ? body : title + "\n" + body; -// byte[] data = createTextPackage(displayText, 1, 1); -// sendDataSequentially(data); -// } - - public void displayReferenceCardSimple(String title, String body, int lingerTime) { - if (!isConnected()) { - Log.d(TAG, "Not connected to glasses"); - return; - } - - // Format text - String displayText = title.isEmpty() ? body : title + "\n" + body; - - // Create and send package - byte[] data = createTextPackage(displayText, 1, 1); - Log.d(TAG, "Sending package: " + bytesToHex(data)); - sendDataSequentially(data); - } - - - - - - - - ///////newwwwwwwwwwwwwwwwww - // Add this at class level - private static final long WRITE_TIMEOUT_MS = 1000; - private static final long DELAY_BETWEEN_SENDS_MS = 400; // 0.4 seconds delay between sends - private byte[] createTextPackage(String text, int currentPage, int totalPages) { byte[] textBytes = text.getBytes(); - Log.d(TAG, "Creating text package for text of length: " + textBytes.length); - ByteBuffer buffer = ByteBuffer.allocate(9 + textBytes.length); - buffer.put((byte)0x4E); // Command - buffer.put((byte)(currentSeq++ & 0xFF)); // Sequence number - buffer.put((byte)1); // Total packages (just sending as 1 for now) - buffer.put((byte)0); // Current package -// buffer.put((byte)0x31); // Screen status (0x30 | 0x01) - buffer.put((byte)0x51); // Only display new content without Even AI - buffer.put((byte)0); // new_char_pos0 - buffer.put((byte)0); // new_char_pos1 - buffer.put((byte)currentPage); // Current page number - buffer.put((byte)totalPages); // Total pages - buffer.put(textBytes); // Text data - - byte[] result = buffer.array(); - Log.d(TAG, "Created package: " + bytesToHex(result)); - return result; + buffer.put((byte) 0x4E); + buffer.put((byte) (currentSeq++ & 0xFF)); + buffer.put((byte) 1); + buffer.put((byte) 0); + buffer.put((byte) 0x31); // New content display + buffer.put((byte) 0); + buffer.put((byte) 0); + buffer.put((byte) currentPage); + buffer.put((byte) totalPages); + buffer.put(textBytes); + + return buffer.array(); } private void sendDataSequentially(byte[] data) { - if (stopper) { - return; - } + if (stopper) return; stopper = true; new Thread(() -> { try { if (leftGlassGatt != null && leftTxChar != null) { leftTxChar.setValue(data); - if (!leftGlassGatt.writeCharacteristic(leftTxChar)) { - stopper = false; - return; - } + if (!leftGlassGatt.writeCharacteristic(leftTxChar)) return; Thread.sleep(DELAY_BETWEEN_SENDS_MS); } if (rightGlassGatt != null && rightTxChar != null) { rightTxChar.setValue(data); - if (!rightGlassGatt.writeCharacteristic(rightTxChar)) { - stopper = false; - return; - } - } - Thread.sleep(DELAY_BETWEEN_SENDS_MS); - - byte[] completeData = data.clone(); - completeData[4] = 0x40; - - if (leftGlassGatt != null && leftTxChar != null) { - leftTxChar.setValue(completeData); - leftGlassGatt.writeCharacteristic(leftTxChar); - Thread.sleep(DELAY_BETWEEN_SENDS_MS); - } - - if (rightGlassGatt != null && rightTxChar != null) { - rightTxChar.setValue(completeData); rightGlassGatt.writeCharacteristic(rightTxChar); + Thread.sleep(DELAY_BETWEEN_SENDS_MS); } - - Thread.sleep(DELAY_BETWEEN_SENDS_MS); stopper = false; - - } catch (Exception e) { + } catch (InterruptedException e) { Log.e(TAG, "Error sending data: " + e.getMessage()); - stopper = false; } }).start(); } - // Updated callbacks to include better logging - private final BluetoothGattCallback leftGattCallback = new BluetoothGattCallback() { - @Override - public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { - Log.d(TAG, "Left glass connection state change: status=" + status + " newState=" + newState); - if (newState == BluetoothProfile.STATE_CONNECTED) { - Log.d(TAG, "Left glass connected, discovering services..."); - gatt.discoverServices(); - isLeftConnected = true; - updateConnectionState(); - } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { - isLeftConnected = false; - updateConnectionState(); - Log.d(TAG, "Left glass disconnected"); - } - } - - @Override - public void onServicesDiscovered(BluetoothGatt gatt, int status) { - Log.d(TAG, "Left glass services discovered: status=" + status); - if (status == BluetoothGatt.GATT_SUCCESS) { - BluetoothGattService uartService = gatt.getService(UART_SERVICE_UUID); - if (uartService != null) { - leftTxChar = uartService.getCharacteristic(UART_TX_CHAR_UUID); - if (leftTxChar != null) { - Log.d(TAG, "Left glass TX characteristic found"); - } else { - Log.e(TAG, "Left glass TX characteristic not found"); - } - } else { - Log.e(TAG, "Left glass UART service not found"); - } - } - } - - @Override - public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { - Log.d(TAG, "Left glass characteristic write: status=" + status); - if (status == BluetoothGatt.GATT_SUCCESS) { - Log.d(TAG, "Left glass write successful"); - } else { - Log.e(TAG, "Left glass write failed"); - } - } - }; - - // Similar updates for rightGattCallback... - private static String bytesToHex(byte[] bytes) { - StringBuilder sb = new StringBuilder(); - for (byte b : bytes) { - sb.append(String.format("%02X ", b)); - } - return sb.toString(); - } - - @Override - public void displayTextWall(String text) { + public void displayReferenceCardSimple(String title, String body, int lingerTime) { if (!isConnected()) { Log.d(TAG, "Not connected to glasses"); return; } - - byte[] data = createTextPackage(text, 1, 1); + String displayText = title.isEmpty() ? body : title + "\n" + body; + byte[] data = createTextPackage(displayText, 1, 1); sendDataSequentially(data); } - @Override - protected void setFontSizes() { - // Empty implementation - } - @Override public void destroy() { if (leftGlassGatt != null) { @@ -351,58 +222,146 @@ public void destroy() { rightGlassGatt.disconnect(); rightGlassGatt.close(); } - } - @Override - public boolean isConnected() { - return mConnectState == 2; + //clean up heartbeat + stopHeartbeat(); } - // Empty implementations of other required methods - public void displayCenteredText(String text) {} - public void showNaturalLanguageCommandScreen(String prompt, String naturalLanguageInput) {} - public void updateNaturalLanguageCommandScreen(String naturalLanguageArgs) {} + private static String bytesToHex(byte[] bytes) { + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02X ", b)); + } + return sb.toString().trim(); + } @Override - public void scrollingTextViewIntermediateText(String text) { - + public void displayReferenceCardSimple(String title, String body) { + displayReferenceCardSimple(title, body, 20); } - @Override - public void scrollingTextViewFinalText(String text) { +// public void displayReferenceCardSimple(String title, String body, int lingerTime) { +// if (!isConnected()) { +// Log.d(TAG, "Not connected to glasses"); +// return; +// } +// +// // Combine title and body into a single text payload +// String displayText = title.isEmpty() ? body : title + "\n" + body; +// +// // Create a text package to send +// byte[] data = createTextPackage(displayText, 1, 1); // Assuming single page for simplicity +// +// // Log the package being sent +// Log.d(TAG, "Sending text package for displayReferenceCardSimple: " + bytesToHex(data)); +// +// // Send the data to the glasses +// sendDataSequentially(data); +// } + @Override + public boolean isConnected() { + return mConnectState == 2; } + @Override + public void displayCenteredText(String text) {} @Override - public void stopScrollingTextViewMode() { + public void showNaturalLanguageCommandScreen(String prompt, String naturalLanguageInput) {} - } + @Override + public void updateNaturalLanguageCommandScreen(String naturalLanguageArgs) {} @Override - public void displayPromptView(String title, String[] options) { + public void scrollingTextViewIntermediateText(String text) {} - } + @Override + public void scrollingTextViewFinalText(String text) {} @Override - public void displayTextLine(String text) { + public void stopScrollingTextViewMode() {} - } + @Override + public void displayPromptView(String title, String[] options) {} @Override - public void displayBitmap(Bitmap bmp) { + public void displayTextLine(String text) {} - } + @Override + public void displayBitmap(Bitmap bmp) {} public void blankScreen() {} + public void displayDoubleTextWall(String textTop, String textBottom) {} + public void showHomeScreen() {} @Override - public void setFontSize(SmartGlassesFontSize fontSize) { - - } + public void setFontSize(SmartGlassesFontSize fontSize) {} public void displayRowsCard(String[] rowStrings) {} + public void displayBulletList(String title, String[] bullets) {} + public void displayReferenceCardImage(String title, String body, String imgUrl) {} + public void displayTextWall(String a) {} + public void setFontSizes(){} + + //heartbeat stuff + // Add these variables at the top of the class + private static final long HEARTBEAT_INTERVAL_MS = 5000; // 5 seconds + private Handler heartbeatHandler = new Handler(); + private Runnable heartbeatRunnable; + + // Heartbeat packet construction + private byte[] constructHeartbeat() { + ByteBuffer buffer = ByteBuffer.allocate(6); + buffer.put((byte) 0x25); // Heartbeat command + buffer.put((byte) 6); // Packet length + buffer.put((byte) (currentSeq & 0xFF)); // Sequence number + buffer.put((byte) 0x00); // Reserved + buffer.put((byte) 0x04); // Heartbeat flag + buffer.put((byte) (currentSeq++ & 0xFF)); // Sequence number again + return buffer.array(); + } + + // Start the heartbeat timer + private void startHeartbeat() { + heartbeatRunnable = new Runnable() { + @Override + public void run() { + sendHeartbeat(); + heartbeatHandler.postDelayed(this, HEARTBEAT_INTERVAL_MS); + } + }; + heartbeatHandler.post(heartbeatRunnable); + } + + // Stop the heartbeat timer + private void stopHeartbeat() { + if (heartbeatHandler != null) { + heartbeatHandler.removeCallbacks(heartbeatRunnable); + } + } + + // Send heartbeat packets + private void sendHeartbeat() { + byte[] heartbeatPacket = constructHeartbeat(); + Log.d(TAG, "Sending heartbeat: " + bytesToHex(heartbeatPacket)); + + new Thread(() -> { + try { + if (leftGlassGatt != null && leftTxChar != null) { + leftTxChar.setValue(heartbeatPacket); + leftGlassGatt.writeCharacteristic(leftTxChar); + } + if (rightGlassGatt != null && rightTxChar != null) { + rightTxChar.setValue(heartbeatPacket); + rightGlassGatt.writeCharacteristic(rightTxChar); + } + } catch (Exception e) { + Log.e(TAG, "Error sending heartbeat: " + e.getMessage()); + } + }).start(); + } } \ No newline at end of file From 11d40536d35ce71ee1cac8593521eb1444a240ad Mon Sep 17 00:00:00 2001 From: cayden Date: Sat, 16 Nov 2024 10:44:21 -0500 Subject: [PATCH 03/11] keep fighting g1's broken SDK --- .../EvenRealitiesG1SGC.java | 150 +++++++++++------- 1 file changed, 90 insertions(+), 60 deletions(-) diff --git a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java index d117b008..e4ab9ad7 100644 --- a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java +++ b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java @@ -16,6 +16,17 @@ import java.util.UUID; import java.util.concurrent.Semaphore; +import android.bluetooth.*; +import android.content.*; +import android.graphics.Bitmap; +import android.os.Handler; +import android.util.Log; + +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.util.UUID; +import java.util.concurrent.Semaphore; + public class EvenRealitiesG1SGC extends SmartGlassesCommunicator { private static final String TAG = "WearableAi_EvenRealitiesG1SGC"; @@ -39,6 +50,10 @@ public class EvenRealitiesG1SGC extends SmartGlassesCommunicator { private boolean stopper = false; private static final long DELAY_BETWEEN_SENDS_MS = 400; + private static final long HEARTBEAT_INTERVAL_MS = 5000; + + private Handler heartbeatHandler = new Handler(); + private Runnable heartbeatRunnable; public EvenRealitiesG1SGC(Context context) { super(); @@ -111,7 +126,7 @@ public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteris Log.d(TAG, "Received response: " + bytesToHex(data)); // Check if it's a heartbeat response - if (data.length > 0 && data[0] == 0x25) { // Check for heartbeat response + if (data.length > 0 && data[0] == 0x25) { Log.d(TAG, "Heartbeat response received"); } } @@ -119,7 +134,6 @@ public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteris }; } - private void enableNotification(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { gatt.setCharacteristicNotification(characteristic, true); BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_UUID); @@ -142,6 +156,43 @@ private void updateConnectionState() { } } + // Pairing functionality + private void pairDevice(BluetoothDevice device) { + try { + Log.d(TAG, "Attempting to pair with device: " + device.getName()); + Method method = device.getClass().getMethod("createBond"); + method.invoke(device); + } catch (Exception e) { + Log.e(TAG, "Pairing failed: " + e.getMessage()); + } + } + + private final BroadcastReceiver pairingReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) { + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1); + + if (bondState == BluetoothDevice.BOND_BONDED) { + Log.d(TAG, "Paired with device: " + device.getName()); + connectToGatt(device); + } else if (bondState == BluetoothDevice.BOND_NONE) { + Log.d(TAG, "Pairing failed or unpaired: " + device.getName()); + } + } + } + }; + + private void connectToGatt(BluetoothDevice device) { + if (device.getName().contains("_L_") && leftGlassGatt == null) { + leftGlassGatt = device.connectGatt(context, false, leftGattCallback); + } else if (device.getName().contains("_R_") && rightGlassGatt == null) { + rightGlassGatt = device.connectGatt(context, false, rightGattCallback); + } + } + @Override public void connectToSmartGlasses() { BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); @@ -149,10 +200,11 @@ public void connectToSmartGlasses() { BluetoothAdapter.LeScanCallback leScanCallback = (device, rssi, scanRecord) -> { String name = device.getName(); if (name != null && name.contains("G1")) { - if (name.contains("_L_") && leftGlassGatt == null) { - leftGlassGatt = device.connectGatt(context, false, leftGattCallback); - } else if (name.contains("_R_") && rightGlassGatt == null) { - rightGlassGatt = device.connectGatt(context, false, rightGattCallback); + int bondState = device.getBondState(); + if (bondState != BluetoothDevice.BOND_BONDED) { + pairDevice(device); + } else { + connectToGatt(device); } } }; @@ -161,14 +213,14 @@ public void connectToSmartGlasses() { handler.postDelayed(() -> bluetoothAdapter.stopLeScan(leScanCallback), 10000); } - private byte[] createTextPackage(String text, int currentPage, int totalPages) { + private byte[] createTextPackage(String text, int currentPage, int totalPages, int screenStatus) { byte[] textBytes = text.getBytes(); ByteBuffer buffer = ByteBuffer.allocate(9 + textBytes.length); buffer.put((byte) 0x4E); buffer.put((byte) (currentSeq++ & 0xFF)); buffer.put((byte) 1); buffer.put((byte) 0); - buffer.put((byte) 0x31); // New content display + buffer.put((byte) screenStatus); buffer.put((byte) 0); buffer.put((byte) 0); buffer.put((byte) currentPage); @@ -186,7 +238,7 @@ private void sendDataSequentially(byte[] data) { try { if (leftGlassGatt != null && leftTxChar != null) { leftTxChar.setValue(data); - if (!leftGlassGatt.writeCharacteristic(leftTxChar)) return; + leftGlassGatt.writeCharacteristic(leftTxChar); Thread.sleep(DELAY_BETWEEN_SENDS_MS); } @@ -202,14 +254,21 @@ private void sendDataSequentially(byte[] data) { }).start(); } + @Override + public void displayReferenceCardSimple(String title, String body) { + displayReferenceCardSimple(title, body, 20); + } + public void displayReferenceCardSimple(String title, String body, int lingerTime) { if (!isConnected()) { Log.d(TAG, "Not connected to glasses"); return; } String displayText = title.isEmpty() ? body : title + "\n" + body; - byte[] data = createTextPackage(displayText, 1, 1); + byte[] data = createTextPackage(displayText, 1, 1, 0x30); sendDataSequentially(data); +// data = createTextPackage(displayText, 1, 1, 0x40); +// sendDataSequentially(data); } @Override @@ -223,46 +282,16 @@ public void destroy() { rightGlassGatt.close(); } - //clean up heartbeat + context.unregisterReceiver(pairingReceiver); stopHeartbeat(); } - private static String bytesToHex(byte[] bytes) { - StringBuilder sb = new StringBuilder(); - for (byte b : bytes) { - sb.append(String.format("%02X ", b)); - } - return sb.toString().trim(); - } - - @Override - public void displayReferenceCardSimple(String title, String body) { - displayReferenceCardSimple(title, body, 20); - } - -// public void displayReferenceCardSimple(String title, String body, int lingerTime) { -// if (!isConnected()) { -// Log.d(TAG, "Not connected to glasses"); -// return; -// } -// -// // Combine title and body into a single text payload -// String displayText = title.isEmpty() ? body : title + "\n" + body; -// -// // Create a text package to send -// byte[] data = createTextPackage(displayText, 1, 1); // Assuming single page for simplicity -// -// // Log the package being sent -// Log.d(TAG, "Sending text package for displayReferenceCardSimple: " + bytesToHex(data)); -// -// // Send the data to the glasses -// sendDataSequentially(data); -// } - @Override public boolean isConnected() { return mConnectState == 2; } + + // Remaining methods @Override public void displayCenteredText(String text) {} @@ -304,28 +333,23 @@ public void displayRowsCard(String[] rowStrings) {} public void displayBulletList(String title, String[] bullets) {} public void displayReferenceCardImage(String title, String body, String imgUrl) {} + public void displayTextWall(String a) {} - public void setFontSizes(){} - //heartbeat stuff - // Add these variables at the top of the class - private static final long HEARTBEAT_INTERVAL_MS = 5000; // 5 seconds - private Handler heartbeatHandler = new Handler(); - private Runnable heartbeatRunnable; + public void setFontSizes() {} - // Heartbeat packet construction + // Heartbeat methods private byte[] constructHeartbeat() { ByteBuffer buffer = ByteBuffer.allocate(6); - buffer.put((byte) 0x25); // Heartbeat command - buffer.put((byte) 6); // Packet length - buffer.put((byte) (currentSeq & 0xFF)); // Sequence number - buffer.put((byte) 0x00); // Reserved - buffer.put((byte) 0x04); // Heartbeat flag - buffer.put((byte) (currentSeq++ & 0xFF)); // Sequence number again + buffer.put((byte) 0x25); + buffer.put((byte) 6); + buffer.put((byte) (currentSeq & 0xFF)); + buffer.put((byte) 0x00); + buffer.put((byte) 0x04); + buffer.put((byte) (currentSeq++ & 0xFF)); return buffer.array(); } - // Start the heartbeat timer private void startHeartbeat() { heartbeatRunnable = new Runnable() { @Override @@ -337,14 +361,12 @@ public void run() { heartbeatHandler.post(heartbeatRunnable); } - // Stop the heartbeat timer private void stopHeartbeat() { if (heartbeatHandler != null) { heartbeatHandler.removeCallbacks(heartbeatRunnable); } } - // Send heartbeat packets private void sendHeartbeat() { byte[] heartbeatPacket = constructHeartbeat(); Log.d(TAG, "Sending heartbeat: " + bytesToHex(heartbeatPacket)); @@ -364,4 +386,12 @@ private void sendHeartbeat() { } }).start(); } -} \ No newline at end of file + + private static String bytesToHex(byte[] bytes) { + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02X ", b)); + } + return sb.toString().trim(); + } +} From 84029544a1d35ebb69d41f1655a6d0fc881da6c4 Mon Sep 17 00:00:00 2001 From: cayden Date: Sat, 16 Nov 2024 13:50:26 -0500 Subject: [PATCH 04/11] try switching to notify on g1, also fails, g1 sdk is broken --- .../EvenRealitiesG1SGC.java | 121 +++++++++++++++++- 1 file changed, 115 insertions(+), 6 deletions(-) diff --git a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java index e4ab9ad7..10b3fd89 100644 --- a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java +++ b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java @@ -11,8 +11,15 @@ import android.graphics.Bitmap; import android.os.Handler; import android.util.Log; +import com.google.gson.Gson; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; import java.util.UUID; import java.util.concurrent.Semaphore; @@ -48,8 +55,9 @@ public class EvenRealitiesG1SGC extends SmartGlassesCommunicator { private boolean isRightConnected = false; private int currentSeq = 0; private boolean stopper = false; + private boolean debugStopper = false; - private static final long DELAY_BETWEEN_SENDS_MS = 400; + private static final long DELAY_BETWEEN_SENDS_MS = 1; private static final long HEARTBEAT_INTERVAL_MS = 5000; private Handler heartbeatHandler = new Handler(); @@ -93,6 +101,7 @@ public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (txChar != null) { if ("Left".equals(side)) leftTxChar = txChar; else rightTxChar = txChar; + txChar.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT); // Add this line Log.d(TAG, side + " glass TX characteristic found"); } @@ -103,6 +112,8 @@ public void onServicesDiscovered(BluetoothGatt gatt, int status) { Log.d(TAG, side + " glass RX characteristic found"); } + + startHeartbeat(); } else { Log.e(TAG, side + " glass UART service not found"); @@ -259,18 +270,116 @@ public void displayReferenceCardSimple(String title, String body) { displayReferenceCardSimple(title, body, 20); } + private static final int NOTIFICATION = 0x4B; // Notification command + + private String createNotificationJson(String appIdentifier, String title, String subtitle, String message) { + Notification notification = new Notification(); + notification.ncs_notification = new NCSNotification( + 1, // msg_id + 1, // type + appIdentifier, + title, + subtitle, + message, + System.currentTimeMillis() / 1000L, // time_s + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()), // date + "Test Notification" // display_name + ); + notification.type = "Add"; + + Gson gson = new Gson(); + return gson.toJson(notification); + } + + // Classes for Notification and NCSNotification + class Notification { + NCSNotification ncs_notification; + String type; + } + + class NCSNotification { + int msg_id; + int type; + String app_identifier; + String title; + String subtitle; + String message; + long time_s; + String date; + String display_name; + + public NCSNotification(int msg_id, int type, String app_identifier, String title, String subtitle, String message, long time_s, String date, String display_name) { + this.msg_id = msg_id; + this.type = type; + this.app_identifier = app_identifier; + this.title = title; + this.subtitle = subtitle; + this.message = message; + this.time_s = time_s; + this.date = date; + this.display_name = display_name; + } + } + + private List createNotificationChunks(String json) { + final int MAX_CHUNK_SIZE = 176; // 180 - 4 header bytes + byte[] jsonBytes = json.getBytes(StandardCharsets.UTF_8); + int totalChunks = (int) Math.ceil((double) jsonBytes.length / MAX_CHUNK_SIZE); + + List chunks = new ArrayList<>(); + for (int i = 0; i < totalChunks; i++) { + int start = i * MAX_CHUNK_SIZE; + int end = Math.min(start + MAX_CHUNK_SIZE, jsonBytes.length); + byte[] payloadChunk = Arrays.copyOfRange(jsonBytes, start, end); + + // Create the header + byte[] header = new byte[] { + (byte) NOTIFICATION, + 0x00, // notify_id (can be updated as needed) + (byte) totalChunks, + (byte) i + }; + + // Combine header and payload + ByteBuffer chunk = ByteBuffer.allocate(header.length + payloadChunk.length); + chunk.put(header); + chunk.put(payloadChunk); + + chunks.add(chunk.array()); + } + + return chunks; + } + public void displayReferenceCardSimple(String title, String body, int lingerTime) { if (!isConnected()) { Log.d(TAG, "Not connected to glasses"); return; } - String displayText = title.isEmpty() ? body : title + "\n" + body; - byte[] data = createTextPackage(displayText, 1, 1, 0x30); - sendDataSequentially(data); -// data = createTextPackage(displayText, 1, 1, 0x40); -// sendDataSequentially(data); + if (debugStopper){ + return; + } + debugStopper = true; + + // Create JSON and chunks +// String json = createNotificationJson("org.telegram.messenger", "Title", "This is sick", "and this is good too"); + String json = createNotificationJson("org.telegram.messenger", "Test", "message", "Short message"); + Log.d(TAG, "G1 Generated JSON: " + json); + List chunks = createNotificationChunks(json); + + // Log chunks for debugging + for (byte[] chunk : chunks) { + Log.d(TAG, "Chunk: " + Arrays.toString(chunk)); + } + + // Send each chunk sequentially + for (byte[] chunk : chunks) { + sendDataSequentially(chunk); + } } + + @Override public void destroy() { if (leftGlassGatt != null) { From 90b3238bbf34d3141ecefbc4a9e7163d05903589 Mon Sep 17 00:00:00 2001 From: cayden Date: Thu, 28 Nov 2024 18:13:12 -0500 Subject: [PATCH 05/11] g1 audio streaming working, start working more on notifications --- .../EvenRealitiesG1SGC.java | 140 ++++++++++++++++-- 1 file changed, 128 insertions(+), 12 deletions(-) diff --git a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java index 10b3fd89..e72b4e42 100644 --- a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java +++ b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java @@ -10,6 +10,7 @@ import android.content.Context; import android.graphics.Bitmap; import android.os.Handler; +import android.os.Looper; import android.util.Log; import com.google.gson.Gson; @@ -60,9 +61,14 @@ public class EvenRealitiesG1SGC extends SmartGlassesCommunicator { private static final long DELAY_BETWEEN_SENDS_MS = 1; private static final long HEARTBEAT_INTERVAL_MS = 5000; + //heartbeat sender private Handler heartbeatHandler = new Handler(); private Runnable heartbeatRunnable; + //notification period sender + private Handler notificationHandler = new Handler(); + private Runnable notificationRunnable; + public EvenRealitiesG1SGC(Context context) { super(); this.context = context; @@ -93,7 +99,13 @@ public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { + + gatt.requestMtu(251); // Request a higher MTU size + Log.d(TAG, "Requested MTU size: 251"); + BluetoothGattService uartService = gatt.getService(UART_SERVICE_UUID); + + if (uartService != null) { BluetoothGattCharacteristic txChar = uartService.getCharacteristic(UART_TX_CHAR_UUID); BluetoothGattCharacteristic rxChar = uartService.getCharacteristic(UART_RX_CHAR_UUID); @@ -101,20 +113,38 @@ public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (txChar != null) { if ("Left".equals(side)) leftTxChar = txChar; else rightTxChar = txChar; - txChar.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT); // Add this line + txChar.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT); Log.d(TAG, side + " glass TX characteristic found"); } if (rxChar != null) { + + // Bond the device + if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_BONDED) { + Log.d(TAG, "Creating bond with device: " + gatt.getDevice().getName()); + gatt.getDevice().createBond(); + } else { + Log.d(TAG, "Device already bonded: " + gatt.getDevice().getName()); + } + if ("Left".equals(side)) leftRxChar = rxChar; else rightRxChar = rxChar; enableNotification(gatt, rxChar); Log.d(TAG, side + " glass RX characteristic found"); } + // Request MTU size + gatt.requestMtu(251); + Log.d(TAG, "Requested MTU size: 251"); - + //start heartbeat startHeartbeat(); + + // Start MIC streaming + setMicEnabled(true); // Enable the MIC + + //start sending notifications + startPeriodicNotifications(); } else { Log.e(TAG, side + " glass UART service not found"); } @@ -132,16 +162,28 @@ public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristi @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { - if (characteristic.getUuid().equals(UART_RX_CHAR_UUID)) { - byte[] data = characteristic.getValue(); - Log.d(TAG, "Received response: " + bytesToHex(data)); + new Handler(Looper.getMainLooper()).post(() -> { + if (characteristic.getUuid().equals(UART_RX_CHAR_UUID)) { + byte[] data = characteristic.getValue(); + + // Handle MIC audio data + if (data.length > 0 && (data[0] & 0xFF) == 0xF1) { + int seq = data[1] & 0xFF; // Sequence number + byte[] audioData = Arrays.copyOfRange(data, 2, data.length); // Extract audio data +// Log.d(TAG, "Audio data received. Seq: " + seq + ", Data: " + Arrays.toString(audioData)); + } else { + Log.d(TAG, "Received non-audio response: " + bytesToHex(data)); + } - // Check if it's a heartbeat response - if (data.length > 0 && data[0] == 0x25) { - Log.d(TAG, "Heartbeat response received"); + // Check if it's a heartbeat response + if (data.length > 0 && data[0] == 0x25) { + Log.d(TAG, "Heartbeat response received"); + } } - } + }); } + + }; } @@ -150,19 +192,29 @@ private void enableNotification(BluetoothGatt gatt, BluetoothGattCharacteristic BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_UUID); if (descriptor != null) { descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); - gatt.writeDescriptor(descriptor); + boolean result = gatt.writeDescriptor(descriptor); + if (result) { + Log.d(TAG, "Descriptor write successful for characteristic: " + characteristic.getUuid()); + } else { + Log.e(TAG, "Failed to write descriptor for characteristic: " + characteristic.getUuid()); + } + } else { + Log.e(TAG, "Descriptor not found for characteristic: " + characteristic.getUuid()); } } private void updateConnectionState() { if (isLeftConnected && isRightConnected) { mConnectState = 2; + Log.d(TAG, "Both glasses connected"); connectionEvent(2); } else if (isLeftConnected || isRightConnected) { mConnectState = 1; + Log.d(TAG, "One glass connected"); connectionEvent(1); } else { mConnectState = 0; + Log.d(TAG, "No glasses connected"); connectionEvent(0); } } @@ -378,10 +430,9 @@ public void displayReferenceCardSimple(String title, String body, int lingerTime } } - - @Override public void destroy() { + setMicEnabled(false); // Disable the MIC if (leftGlassGatt != null) { leftGlassGatt.disconnect(); leftGlassGatt.close(); @@ -391,10 +442,14 @@ public void destroy() { rightGlassGatt.close(); } + // Stop periodic notifications + stopPeriodicNotifications(); + context.unregisterReceiver(pairingReceiver); stopHeartbeat(); } + @Override public boolean isConnected() { return mConnectState == 2; @@ -503,4 +558,65 @@ private static String bytesToHex(byte[] bytes) { } return sb.toString().trim(); } + + //microphone stuff + public void setMicEnabled(boolean enable) { + if (!isConnected()) { + Log.d(TAG, "Tryna start mic: Not connected to glasses"); + return; + } + + byte command = 0x0E; // Command for MIC control + byte enableByte = (byte) (enable ? 1 : 0); // 1 to enable, 0 to disable + + ByteBuffer buffer = ByteBuffer.allocate(2); + buffer.put(command); + buffer.put(enableByte); + + sendDataSequentially(buffer.array()); + Log.d(TAG, "Sent MIC command: " + bytesToHex(buffer.array())); + } + + //notifications + private void startPeriodicNotifications() { + notificationRunnable = new Runnable() { + @Override + public void run() { + // Send notification + sendPeriodicNotification(); + + // Schedule the next notification + notificationHandler.postDelayed(this, 5000); // 5 seconds + } + }; + + // Start the first notification after 5 seconds + notificationHandler.postDelayed(notificationRunnable, 5000); + } + + private void sendPeriodicNotification() { + if (!isConnected()) { + Log.d(TAG, "Cannot send notification: Not connected to glasses"); + return; + } + + // Example notification data (replace with your actual data) + String json = createNotificationJson("com.even.test", "Periodic Notification", "Hello", "This is a recurring notification."); + List chunks = createNotificationChunks(json); + + // Send each chunk + for (byte[] chunk : chunks) { + sendDataSequentially(chunk); + } + + Log.d(TAG, "Sent periodic notification"); + } + + private void stopPeriodicNotifications() { + if (notificationHandler != null && notificationRunnable != null) { + notificationHandler.removeCallbacks(notificationRunnable); + Log.d(TAG, "Stopped periodic notifications"); + } + } + } From 16fdc0188e017700a60da1ac66ea9b05a0bb83ac Mon Sep 17 00:00:00 2001 From: cayden Date: Thu, 28 Nov 2024 19:56:54 -0500 Subject: [PATCH 06/11] microphone streaming, notifications working on G1 --- .../EvenRealitiesG1SGC.java | 145 ++++++++++++------ 1 file changed, 96 insertions(+), 49 deletions(-) diff --git a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java index e72b4e42..f776d17b 100644 --- a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java +++ b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java @@ -14,6 +14,7 @@ import android.util.Log; import com.google.gson.Gson; +import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; @@ -68,6 +69,7 @@ public class EvenRealitiesG1SGC extends SmartGlassesCommunicator { //notification period sender private Handler notificationHandler = new Handler(); private Runnable notificationRunnable; + private boolean notifysStarted = false; public EvenRealitiesG1SGC(Context context) { super(); @@ -113,6 +115,7 @@ public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (txChar != null) { if ("Left".equals(side)) leftTxChar = txChar; else rightTxChar = txChar; + enableNotification(gatt, rxChar, side); txChar.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT); Log.d(TAG, side + " glass TX characteristic found"); } @@ -129,7 +132,8 @@ public void onServicesDiscovered(BluetoothGatt gatt, int status) { if ("Left".equals(side)) leftRxChar = rxChar; else rightRxChar = rxChar; - enableNotification(gatt, rxChar); + enableNotification(gatt, rxChar, side); + txChar.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT); Log.d(TAG, side + " glass RX characteristic found"); } @@ -137,11 +141,15 @@ public void onServicesDiscovered(BluetoothGatt gatt, int status) { gatt.requestMtu(251); Log.d(TAG, "Requested MTU size: 251"); + //no idea why but it's in the Even app - Cayden + Log.d(TAG, "Sending 0xF4 Command"); + sendDataSequentially(new byte[] {(byte) 0xF4, (byte) 0x01}); + //start heartbeat - startHeartbeat(); +// startHeartbeat(); // Start MIC streaming - setMicEnabled(true); // Enable the MIC +// setMicEnabled(true); // Enable the MIC //start sending notifications startPeriodicNotifications(); @@ -187,19 +195,19 @@ public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteris }; } - private void enableNotification(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + private void enableNotification(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, String side) { gatt.setCharacteristicNotification(characteristic, true); BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_UUID); if (descriptor != null) { descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); boolean result = gatt.writeDescriptor(descriptor); if (result) { - Log.d(TAG, "Descriptor write successful for characteristic: " + characteristic.getUuid()); + Log.d(TAG, side + " SIDE," + "Descriptor write successful for characteristic: " + characteristic.getUuid()); } else { - Log.e(TAG, "Failed to write descriptor for characteristic: " + characteristic.getUuid()); + Log.e(TAG, side + " SIDE," + "Failed to write descriptor for characteristic: " + characteristic.getUuid()); } } else { - Log.e(TAG, "Descriptor not found for characteristic: " + characteristic.getUuid()); + Log.e(TAG, side + " SIDE," + "Descriptor not found for characteristic: " + characteristic.getUuid()); } } @@ -323,30 +331,41 @@ public void displayReferenceCardSimple(String title, String body) { } private static final int NOTIFICATION = 0x4B; // Notification command - private String createNotificationJson(String appIdentifier, String title, String subtitle, String message) { - Notification notification = new Notification(); - notification.ncs_notification = new NCSNotification( - 1, // msg_id - 1, // type + long currentTime = System.currentTimeMillis() / 1000L; // Unix timestamp in seconds + String currentDate = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date()); // Date format for 'date' field + + NCSNotification ncsNotification = new NCSNotification( + 6, // Increment sequence ID for uniqueness + 1, // type (e.g., 1 = notification type) appIdentifier, title, subtitle, message, - System.currentTimeMillis() / 1000L, // time_s - new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()), // date - "Test Notification" // display_name + (int) currentTime, // Cast long to int to match Python + currentDate, // Add the current date to the notification + "AugmentOS" // display_name ); - notification.type = "Add"; + + Notification notification = new Notification(ncsNotification, "Add"); Gson gson = new Gson(); return gson.toJson(notification); } - // Classes for Notification and NCSNotification + class Notification { NCSNotification ncs_notification; String type; + + public Notification() { + // Default constructor + } + + public Notification(NCSNotification ncs_notification, String type) { + this.ncs_notification = ncs_notification; + this.type = type; + } } class NCSNotification { @@ -356,11 +375,11 @@ class NCSNotification { String title; String subtitle; String message; - long time_s; - String date; + int time_s; // Changed from long to int for consistency + String date; // Added to match Python's date field String display_name; - public NCSNotification(int msg_id, int type, String app_identifier, String title, String subtitle, String message, long time_s, String date, String display_name) { + public NCSNotification(int msg_id, int type, String app_identifier, String title, String subtitle, String message, int time_s, String date, String display_name) { this.msg_id = msg_id; this.type = type; this.app_identifier = app_identifier; @@ -368,11 +387,14 @@ public NCSNotification(int msg_id, int type, String app_identifier, String title this.subtitle = subtitle; this.message = message; this.time_s = time_s; - this.date = date; + this.date = date; // Initialize the date field this.display_name = display_name; } } + + + private List createNotificationChunks(String json) { final int MAX_CHUNK_SIZE = 176; // 180 - 4 header bytes byte[] jsonBytes = json.getBytes(StandardCharsets.UTF_8); @@ -404,30 +426,30 @@ private List createNotificationChunks(String json) { } public void displayReferenceCardSimple(String title, String body, int lingerTime) { - if (!isConnected()) { - Log.d(TAG, "Not connected to glasses"); - return; - } - if (debugStopper){ - return; - } - debugStopper = true; - - // Create JSON and chunks -// String json = createNotificationJson("org.telegram.messenger", "Title", "This is sick", "and this is good too"); - String json = createNotificationJson("org.telegram.messenger", "Test", "message", "Short message"); - Log.d(TAG, "G1 Generated JSON: " + json); - List chunks = createNotificationChunks(json); - - // Log chunks for debugging - for (byte[] chunk : chunks) { - Log.d(TAG, "Chunk: " + Arrays.toString(chunk)); - } - - // Send each chunk sequentially - for (byte[] chunk : chunks) { - sendDataSequentially(chunk); - } +// if (!isConnected()) { +// Log.d(TAG, "Not connected to glasses"); +// return; +// } +// if (debugStopper){ +// return; +// } +// debugStopper = true; +// +// // Create JSON and chunks +//// String json = createNotificationJson("org.telegram.messenger", "Title", "This is sick", "and this is good too"); +// String json = createNotificationJson("org.telegram.messenger", "Test", "message", "Short message"); +// Log.d(TAG, "G1 Generated JSON: " + json); +// List chunks = createNotificationChunks(json); +// +// // Log chunks for debugging +// for (byte[] chunk : chunks) { +// Log.d(TAG, "Chunk: " + Arrays.toString(chunk)); +// } +// +// // Send each chunk sequentially +// for (byte[] chunk : chunks) { +// sendDataSequentially(chunk); +// } } @Override @@ -445,7 +467,9 @@ public void destroy() { // Stop periodic notifications stopPeriodicNotifications(); - context.unregisterReceiver(pairingReceiver); + if (pairingReceiver != null) { + context.unregisterReceiver(pairingReceiver); + } stopHeartbeat(); } @@ -579,6 +603,11 @@ public void setMicEnabled(boolean enable) { //notifications private void startPeriodicNotifications() { + if (notifysStarted){ + return; + } + notifysStarted = true; + notificationRunnable = new Runnable() { @Override public void run() { @@ -591,7 +620,7 @@ public void run() { }; // Start the first notification after 5 seconds - notificationHandler.postDelayed(notificationRunnable, 5000); + notificationHandler.postDelayed(notificationRunnable, 3000); } private void sendPeriodicNotification() { @@ -601,17 +630,35 @@ private void sendPeriodicNotification() { } // Example notification data (replace with your actual data) - String json = createNotificationJson("com.even.test", "Periodic Notification", "Hello", "This is a recurring notification."); + String json = createNotificationJson("com.even.test", "QuestionAnswerer", "How much caffeine in dark chocolate?", "25 to 50 grams per piece"); + Log.d(TAG, "the JSON to send: " + json); List chunks = createNotificationChunks(json); +// Log.d(TAG, "THE CHUNKS:"); +// Log.d(TAG, chunks.get(0).toString()); +// Log.d(TAG, chunks.get(1).toString()); + for (byte[] chunk : chunks) { + Log.d(TAG, "Sent chunk to glasses: " + bytesToUtf8(chunk)); + } - // Send each chunk + // Send each chunk with a short sleep between each send for (byte[] chunk : chunks) { sendDataSequentially(chunk); + + // Sleep for 100 milliseconds between sending each chunk + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } } Log.d(TAG, "Sent periodic notification"); } + private static String bytesToUtf8(byte[] bytes) { + return new String(bytes, StandardCharsets.UTF_8); + } + private void stopPeriodicNotifications() { if (notificationHandler != null && notificationRunnable != null) { notificationHandler.removeCallbacks(notificationRunnable); From 31df717c3ce6fdba49de5d1e5d25b1ef530ff777 Mon Sep 17 00:00:00 2001 From: cayden Date: Thu, 28 Nov 2024 20:09:35 -0500 Subject: [PATCH 07/11] even realities notifications working more reliably --- .../EvenRealitiesG1SGC.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java index f776d17b..027ca43e 100644 --- a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java +++ b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java @@ -59,8 +59,8 @@ public class EvenRealitiesG1SGC extends SmartGlassesCommunicator { private boolean stopper = false; private boolean debugStopper = false; - private static final long DELAY_BETWEEN_SENDS_MS = 1; - private static final long HEARTBEAT_INTERVAL_MS = 5000; + private static final long DELAY_BETWEEN_SENDS_MS = 20; + private static final long HEARTBEAT_INTERVAL_MS = 12000; //heartbeat sender private Handler heartbeatHandler = new Handler(); @@ -70,6 +70,7 @@ public class EvenRealitiesG1SGC extends SmartGlassesCommunicator { private Handler notificationHandler = new Handler(); private Runnable notificationRunnable; private boolean notifysStarted = false; + private int notificationNum = 10; public EvenRealitiesG1SGC(Context context) { super(); @@ -146,10 +147,10 @@ public void onServicesDiscovered(BluetoothGatt gatt, int status) { sendDataSequentially(new byte[] {(byte) 0xF4, (byte) 0x01}); //start heartbeat -// startHeartbeat(); + startHeartbeat(); // Start MIC streaming -// setMicEnabled(true); // Enable the MIC + setMicEnabled(true); // Enable the MIC //start sending notifications startPeriodicNotifications(); @@ -336,7 +337,7 @@ private String createNotificationJson(String appIdentifier, String title, String String currentDate = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date()); // Date format for 'date' field NCSNotification ncsNotification = new NCSNotification( - 6, // Increment sequence ID for uniqueness + notificationNum++, // Increment sequence ID for uniqueness 1, // type (e.g., 1 = notification type) appIdentifier, title, @@ -344,7 +345,7 @@ private String createNotificationJson(String appIdentifier, String title, String message, (int) currentTime, // Cast long to int to match Python currentDate, // Add the current date to the notification - "AugmentOS" // display_name + "AugmentOS:" + notificationNum // display_name ); Notification notification = new Notification(ncsNotification, "Add"); @@ -646,7 +647,7 @@ private void sendPeriodicNotification() { // Sleep for 100 milliseconds between sending each chunk try { - Thread.sleep(100); + Thread.sleep(150); } catch (InterruptedException e) { e.printStackTrace(); } From cb3a7cc03e1f39e2059aaad4474f563a45b35360 Mon Sep 17 00:00:00 2001 From: cayden Date: Thu, 28 Nov 2024 22:05:23 -0500 Subject: [PATCH 08/11] get around max speed of notifications on G1 (slow them down) --- .../smartglassescommunicators/EvenRealitiesG1SGC.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java index 027ca43e..9e620453 100644 --- a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java +++ b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java @@ -616,7 +616,7 @@ public void run() { sendPeriodicNotification(); // Schedule the next notification - notificationHandler.postDelayed(this, 5000); // 5 seconds + notificationHandler.postDelayed(this, 12000); } }; From 437780b84b8965d557b7ed6762e5d13a74ebaaef Mon Sep 17 00:00:00 2001 From: cayden Date: Fri, 29 Nov 2024 17:19:46 -0500 Subject: [PATCH 09/11] g1 can connect from scratch with no errors, and fast connect speed --- .../SmartGlassesAndroidService.java | 29 +++- .../EvenRealitiesG1SGC.java | 153 ++++++++++++++---- 2 files changed, 147 insertions(+), 35 deletions(-) diff --git a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/SmartGlassesAndroidService.java b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/SmartGlassesAndroidService.java index 9d987444..c701485d 100644 --- a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/SmartGlassesAndroidService.java +++ b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/SmartGlassesAndroidService.java @@ -432,11 +432,13 @@ public int onStartCommand(Intent intent, int flags, int startId) { // Setup for aioConnectSmartGlasses ArrayList smartGlassesDevices = new ArrayList<>(); Handler aioRetryHandler = new Handler(); + Handler connectedCheckerHandler = new Handler(); // Handler for the connected checker + Runnable aioRetryConnectionTask = new Runnable() { @Override public void run() { if (smartGlassesRepresentative == null || smartGlassesRepresentative.getConnectionState() != 2) { // If still disconnected - if(!smartGlassesDevices.isEmpty()){ + if (!smartGlassesDevices.isEmpty()) { Toast.makeText(getApplicationContext(), "Searching for glasses...", Toast.LENGTH_LONG).show(); Log.d(TAG, "TRYING TO CONNECT TO: " + smartGlassesDevices.get(0).deviceModelName); @@ -447,19 +449,33 @@ public void run() { connectToSmartGlasses(smartGlassesDevices.get(0)); smartGlassesDevices.add(smartGlassesDevices.remove(0)); - aioRetryHandler.postDelayed(this, 5000); // Schedule another retry if needed - } - else - { + aioRetryHandler.postDelayed(this, 25000); // Schedule another retry if needed + } else { aioRetryHandler.removeCallbacks(this); Toast.makeText(getApplicationContext(), "No glasses found", Toast.LENGTH_LONG).show(); } } - else { + } + }; + + // Connected checker + Runnable connectedCheckerTask = new Runnable() { + @Override + public void run() { + if (smartGlassesRepresentative != null && smartGlassesRepresentative.getConnectionState() == 2) { // Check if connected Toast.makeText(getApplicationContext(), "Connected to " + smartGlassesRepresentative.smartGlassesDevice.deviceModelName, Toast.LENGTH_LONG).show(); + Log.d(TAG, "Connected to: " + smartGlassesRepresentative.smartGlassesDevice.deviceModelName); + + // Stop all retries and connected checker + aioRetryHandler.removeCallbacks(aioRetryConnectionTask); + connectedCheckerHandler.removeCallbacks(this); + } else { + // Schedule the next check + connectedCheckerHandler.postDelayed(this, 1500); } } }; + public void aioConnectSmartGlasses(){ if (getChosenAsrFramework(this) == ASR_FRAMEWORKS.GOOGLE_ASR_FRAMEWORK) { String apiKey = getApiKey(getApplicationContext()); @@ -485,6 +501,7 @@ public void aioConnectSmartGlasses(){ //start loop aioRetryConnectionTask.run(); + connectedCheckerHandler.post(connectedCheckerTask); // Start connected checker } public void showNoGoogleAsrDialog(){ diff --git a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java index 9e620453..f25195a4 100644 --- a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java +++ b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java @@ -72,6 +72,14 @@ public class EvenRealitiesG1SGC extends SmartGlassesCommunicator { private boolean notifysStarted = false; private int notificationNum = 10; + //pairing logic + private boolean isLeftPairing = false; + private boolean isRightPairing = false; + private boolean isLeftBonded = false; + private boolean isRightBonded = false; + private BluetoothDevice leftDevice = null; + private BluetoothDevice rightDevice = null; + public EvenRealitiesG1SGC(Context context) { super(); this.context = context; @@ -228,18 +236,7 @@ private void updateConnectionState() { } } - // Pairing functionality - private void pairDevice(BluetoothDevice device) { - try { - Log.d(TAG, "Attempting to pair with device: " + device.getName()); - Method method = device.getClass().getMethod("createBond"); - method.invoke(device); - } catch (Exception e) { - Log.e(TAG, "Pairing failed: " + e.getMessage()); - } - } - - private final BroadcastReceiver pairingReceiver = new BroadcastReceiver() { + private final BroadcastReceiver bondingReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); @@ -248,10 +245,31 @@ public void onReceive(Context context, Intent intent) { int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1); if (bondState == BluetoothDevice.BOND_BONDED) { - Log.d(TAG, "Paired with device: " + device.getName()); - connectToGatt(device); + Log.d(TAG, "Bonded with device: " + device.getName()); + if (device.getName().contains("_L_")) { + isLeftBonded = true; + isLeftPairing = false; + } else if (device.getName().contains("_R_")) { + isRightBonded = true; + isRightPairing = false; + } + + // Restart scan for the next device + if (!isLeftBonded || !isRightBonded) { + Log.d(TAG, "Restarting scan to find remaining device..."); + startScan(BluetoothAdapter.getDefaultAdapter()); + } else { + Log.d(TAG, "Both devices bonded. Proceeding with connections..."); + connectToGatt(leftDevice); + connectToGatt(rightDevice); + } } else if (bondState == BluetoothDevice.BOND_NONE) { - Log.d(TAG, "Pairing failed or unpaired: " + device.getName()); + Log.d(TAG, "Bonding failed for device: " + device.getName()); + if (device.getName().contains("_L_")) isLeftPairing = false; + if (device.getName().contains("_R_")) isRightPairing = false; + + // Restart scanning to retry bonding + startScan(BluetoothAdapter.getDefaultAdapter()); } } } @@ -265,24 +283,99 @@ private void connectToGatt(BluetoothDevice device) { } } + private BluetoothAdapter.LeScanCallback leScanCallback = (device, rssi, scanRecord) -> { + String name = device.getName(); + + //check if G1 arm + if (name == null || !name.contains("G1")) { + return; + } + + //figure out which G1 arm it is + boolean isLeft = name.contains("_L_"); + if (isLeft){ + leftDevice = device; + } else{ + rightDevice = device; + } + + //check if we've already bonded/paired + int bondState = device.getBondState(); + if (bondState != BluetoothDevice.BOND_BONDED) { + // Stop scan before initiating bond + stopScan(BluetoothAdapter.getDefaultAdapter()); + + if (isLeft && !isLeftPairing && !isLeftBonded) { + Log.d(TAG, "Bonding with Left Glass..."); + isLeftPairing = true; + bondDevice(device); + } else if (!isLeft && !isRightPairing && !isRightBonded) { + Log.d(TAG, "Bonding with Right Glass..."); + isRightPairing = true; + bondDevice(device); + } + } else { //only runs if we've already setup the bond previously + // Mark the device as bonded + if (isLeft) isLeftBonded = true; + if (!isLeft) isRightBonded = true; + + // Attempt GATT connection only after both sides are bonded + if (isLeftBonded && isRightBonded) { + Log.d(TAG, "Both sides bonded. Ready to connect to GATT."); + attemptGattConnection(leftDevice); + attemptGattConnection(rightDevice); + } + } + }; + @Override public void connectToSmartGlasses() { BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - BluetoothAdapter.LeScanCallback leScanCallback = (device, rssi, scanRecord) -> { - String name = device.getName(); - if (name != null && name.contains("G1")) { - int bondState = device.getBondState(); - if (bondState != BluetoothDevice.BOND_BONDED) { - pairDevice(device); - } else { - connectToGatt(device); - } - } - }; + // Register bonding receiver + IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED); + context.registerReceiver(bondingReceiver, filter); + // Start scanning for devices + startScan(bluetoothAdapter); + } + + private void startScan(BluetoothAdapter bluetoothAdapter) { bluetoothAdapter.startLeScan(leScanCallback); - handler.postDelayed(() -> bluetoothAdapter.stopLeScan(leScanCallback), 10000); + Log.d(TAG, "Started scanning for devices..."); +// handler.postDelayed(() -> stopScan(bluetoothAdapter), 5000); // Stop scan after 5 seconds + } + + private void stopScan(BluetoothAdapter bluetoothAdapter) { + bluetoothAdapter.stopLeScan(leScanCallback); + Log.d(TAG, "Stopped scanning for devices"); + } + + private void bondDevice(BluetoothDevice device) { + try { + Log.d(TAG, "Attempting to bond with device: " + device.getName()); + Method method = device.getClass().getMethod("createBond"); + method.invoke(device); + } catch (Exception e) { + Log.e(TAG, "Bonding failed: " + e.getMessage()); + } + } + + private void attemptGattConnection(BluetoothDevice device) { + if (!isLeftBonded || !isRightBonded) { + Log.d(TAG, "Cannot connect to GATT: Both devices are not bonded yet"); + return; + } + + if (device.getName().contains("_L_") && leftGlassGatt == null) { + Log.d(TAG, "Connecting to GATT for Left Glass..."); + leftGlassGatt = device.connectGatt(context, false, leftGattCallback); + isLeftConnected = true; + } else if (device.getName().contains("_R_") && rightGlassGatt == null) { + Log.d(TAG, "Connecting to GATT for Right Glass..."); + rightGlassGatt = device.connectGatt(context, false, rightGattCallback); + isRightConnected = true; + } } private byte[] createTextPackage(String text, int currentPage, int totalPages, int screenStatus) { @@ -468,9 +561,11 @@ public void destroy() { // Stop periodic notifications stopPeriodicNotifications(); - if (pairingReceiver != null) { - context.unregisterReceiver(pairingReceiver); + if (bondingReceiver != null) { + context.unregisterReceiver(bondingReceiver); } + + //stop sending heartbeat stopHeartbeat(); } From 214e2f59557e9d004e67322cf452699383bfc920 Mon Sep 17 00:00:00 2001 From: cayden Date: Fri, 29 Nov 2024 21:27:22 -0500 Subject: [PATCH 10/11] much more reliable everything for G1, almost have notification white list working, but still not working. Also mic audio is always the same data --- .../EvenRealitiesG1SGC.java | 174 +++++++++++++++--- 1 file changed, 147 insertions(+), 27 deletions(-) diff --git a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java index f25195a4..23505580 100644 --- a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java +++ b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java @@ -66,6 +66,14 @@ public class EvenRealitiesG1SGC extends SmartGlassesCommunicator { private Handler heartbeatHandler = new Handler(); private Runnable heartbeatRunnable; + //white list sender + private Handler whiteListHandler = new Handler(); + private boolean whiteListedAlready = false; + + //mic enable Handler + private Handler micEnableHandler = new Handler(); + private boolean micEnabledAlready = false; + //notification period sender private Handler notificationHandler = new Handler(); private Runnable notificationRunnable; @@ -116,7 +124,6 @@ public void onServicesDiscovered(BluetoothGatt gatt, int status) { BluetoothGattService uartService = gatt.getService(UART_SERVICE_UUID); - if (uartService != null) { BluetoothGattCharacteristic txChar = uartService.getCharacteristic(UART_TX_CHAR_UUID); BluetoothGattCharacteristic rxChar = uartService.getCharacteristic(UART_RX_CHAR_UUID); @@ -154,14 +161,17 @@ public void onServicesDiscovered(BluetoothGatt gatt, int status) { Log.d(TAG, "Sending 0xF4 Command"); sendDataSequentially(new byte[] {(byte) 0xF4, (byte) 0x01}); - //start heartbeat - startHeartbeat(); - // Start MIC streaming - setMicEnabled(true); // Enable the MIC + setMicEnabled(true, 300); // Enable the MIC + + //enable our AugmentOS notification key +// sendWhiteListCommand(650); //start sending notifications - startPeriodicNotifications(); +// startPeriodicNotifications(4200); + + //start heartbeat + startHeartbeat(12000); } else { Log.e(TAG, side + " glass UART service not found"); } @@ -189,7 +199,7 @@ public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteris byte[] audioData = Arrays.copyOfRange(data, 2, data.length); // Extract audio data // Log.d(TAG, "Audio data received. Seq: " + seq + ", Data: " + Arrays.toString(audioData)); } else { - Log.d(TAG, "Received non-audio response: " + bytesToHex(data)); + Log.d(TAG, "Received non-audio response: " + bytesToHex(data) + ", from: " + gatt.getDevice().getName()); } // Check if it's a heartbeat response @@ -260,6 +270,7 @@ public void onReceive(Context context, Intent intent) { startScan(BluetoothAdapter.getDefaultAdapter()); } else { Log.d(TAG, "Both devices bonded. Proceeding with connections..."); + stopScan(BluetoothAdapter.getDefaultAdapter()); connectToGatt(leftDevice); connectToGatt(rightDevice); } @@ -322,6 +333,7 @@ private void connectToGatt(BluetoothDevice device) { // Attempt GATT connection only after both sides are bonded if (isLeftBonded && isRightBonded) { Log.d(TAG, "Both sides bonded. Ready to connect to GATT."); + stopScan(BluetoothAdapter.getDefaultAdapter()); attemptGattConnection(leftDevice); attemptGattConnection(rightDevice); } @@ -343,7 +355,7 @@ public void connectToSmartGlasses() { private void startScan(BluetoothAdapter bluetoothAdapter) { bluetoothAdapter.startLeScan(leScanCallback); Log.d(TAG, "Started scanning for devices..."); -// handler.postDelayed(() -> stopScan(bluetoothAdapter), 5000); // Stop scan after 5 seconds + handler.postDelayed(() -> stopScan(bluetoothAdapter), 60000); // Stop scan after 60 seconds } private void stopScan(BluetoothAdapter bluetoothAdapter) { @@ -396,6 +408,10 @@ private byte[] createTextPackage(String text, int currentPage, int totalPages, i } private void sendDataSequentially(byte[] data) { + sendDataSequentially(data, false); + } + + private void sendDataSequentially(byte[] data, boolean onlyLeft) { if (stopper) return; stopper = true; @@ -407,7 +423,7 @@ private void sendDataSequentially(byte[] data) { Thread.sleep(DELAY_BETWEEN_SENDS_MS); } - if (rightGlassGatt != null && rightTxChar != null) { + if (!onlyLeft && rightGlassGatt != null && rightTxChar != null) { rightTxChar.setValue(data); rightGlassGatt.writeCharacteristic(rightTxChar); Thread.sleep(DELAY_BETWEEN_SENDS_MS); @@ -438,7 +454,7 @@ private String createNotificationJson(String appIdentifier, String title, String message, (int) currentTime, // Cast long to int to match Python currentDate, // Add the current date to the notification - "AugmentOS:" + notificationNum // display_name + "AugmentOS" // display_name ); Notification notification = new Notification(ncsNotification, "Add"); @@ -544,11 +560,42 @@ public void displayReferenceCardSimple(String title, String body, int lingerTime // for (byte[] chunk : chunks) { // sendDataSequentially(chunk); // } + + if (!isConnected()) { + Log.d(TAG, "Cannot send notification: Not connected to glasses"); + return; + } + + // Example notification data (replace with your actual data) +// String json = createNotificationJson("com.augment.os", "QuestionAnswerer", "How much caffeine in dark chocolate?", "25 to 50 grams per piece"); + String json = createNotificationJson("com.even.test", title, "...", body); + Log.d(TAG, "the JSON to send: " + json); + List chunks = createNotificationChunks(json); +// Log.d(TAG, "THE CHUNKS:"); +// Log.d(TAG, chunks.get(0).toString()); +// Log.d(TAG, chunks.get(1).toString()); + for (byte[] chunk : chunks) { + Log.d(TAG, "Sent chunk to glasses: " + bytesToUtf8(chunk)); + } + + // Send each chunk with a short sleep between each send + for (byte[] chunk : chunks) { + sendDataSequentially(chunk); + + // Sleep for 100 milliseconds between sending each chunk + try { + Thread.sleep(150); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + Log.d(TAG, "Sent simple reference card"); } @Override public void destroy() { - setMicEnabled(false); // Disable the MIC + setMicEnabled(false, 0); // Disable the MIC if (leftGlassGatt != null) { leftGlassGatt.disconnect(); leftGlassGatt.close(); @@ -634,7 +681,7 @@ private byte[] constructHeartbeat() { return buffer.array(); } - private void startHeartbeat() { + private void startHeartbeat(int delay) { heartbeatRunnable = new Runnable() { @Override public void run() { @@ -642,7 +689,33 @@ public void run() { heartbeatHandler.postDelayed(this, HEARTBEAT_INTERVAL_MS); } }; - heartbeatHandler.post(heartbeatRunnable); + heartbeatHandler.postDelayed(heartbeatRunnable, delay); + } + + private void sendWhiteListCommand(int delay) { + if (whiteListedAlready){ + return; + } + whiteListedAlready = true; + + Log.d(TAG, "Sending whitelist command"); + whiteListHandler.postDelayed(new Runnable() { + @Override + public void run() { + List chunks = getWhitelistChunks(); + for (byte[] chunk : chunks) { + Log.d(TAG, "Sending this chunk for white list:" + bytesToUtf8(chunk)); + sendDataSequentially(chunk, true); + + // Sleep for 100 milliseconds between sending each chunk + try { + Thread.sleep(150); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + }, delay); } private void stopHeartbeat() { @@ -680,25 +753,30 @@ private static String bytesToHex(byte[] bytes) { } //microphone stuff - public void setMicEnabled(boolean enable) { - if (!isConnected()) { - Log.d(TAG, "Tryna start mic: Not connected to glasses"); - return; - } + public void setMicEnabled(boolean enable, int delay) { + micEnableHandler.postDelayed(new Runnable() { + @Override + public void run() { + if (!isConnected()) { + Log.d(TAG, "Tryna start mic: Not connected to glasses"); + return; + } - byte command = 0x0E; // Command for MIC control - byte enableByte = (byte) (enable ? 1 : 0); // 1 to enable, 0 to disable + byte command = 0x0E; // Command for MIC control + byte enableByte = (byte) (enable ? 1 : 0); // 1 to enable, 0 to disable - ByteBuffer buffer = ByteBuffer.allocate(2); - buffer.put(command); - buffer.put(enableByte); + ByteBuffer buffer = ByteBuffer.allocate(2); + buffer.put(command); + buffer.put(enableByte); - sendDataSequentially(buffer.array()); - Log.d(TAG, "Sent MIC command: " + bytesToHex(buffer.array())); + sendDataSequentially(buffer.array()); + Log.d(TAG, "Sent MIC command: " + bytesToHex(buffer.array())); + } + }, delay); } //notifications - private void startPeriodicNotifications() { + private void startPeriodicNotifications(int delay) { if (notifysStarted){ return; } @@ -716,7 +794,7 @@ public void run() { }; // Start the first notification after 5 seconds - notificationHandler.postDelayed(notificationRunnable, 3000); + notificationHandler.postDelayed(notificationRunnable, delay); } private void sendPeriodicNotification() { @@ -726,6 +804,7 @@ private void sendPeriodicNotification() { } // Example notification data (replace with your actual data) +// String json = createNotificationJson("com.augment.os", "QuestionAnswerer", "How much caffeine in dark chocolate?", "25 to 50 grams per piece"); String json = createNotificationJson("com.even.test", "QuestionAnswerer", "How much caffeine in dark chocolate?", "25 to 50 grams per piece"); Log.d(TAG, "the JSON to send: " + json); List chunks = createNotificationChunks(json); @@ -762,4 +841,45 @@ private void stopPeriodicNotifications() { } } + // handle white list stuff + private static final int WHITELIST_CMD = 0x04; // Command ID for whitelist + public List getWhitelistChunks() { + // Define the hardcoded whitelist JSON + String whitelistJson = "[{\"id\": \"com.augment.os\", \"name\": \"AugmentOS\"}]"; + + Log.d(TAG, "Creating chunks for hardcoded whitelist: " + whitelistJson); + + // Convert JSON to bytes and split into chunks + return createWhitelistChunks(whitelistJson); + } + + // Helper function to split JSON into chunks + private List createWhitelistChunks(String json) { + final int MAX_CHUNK_SIZE = 180 - 4; // Reserve space for the header + byte[] jsonBytes = json.getBytes(StandardCharsets.UTF_8); + int totalChunks = (int) Math.ceil((double) jsonBytes.length / MAX_CHUNK_SIZE); + + List chunks = new ArrayList<>(); + for (int i = 0; i < totalChunks; i++) { + int start = i * MAX_CHUNK_SIZE; + int end = Math.min(start + MAX_CHUNK_SIZE, jsonBytes.length); + byte[] payloadChunk = Arrays.copyOfRange(jsonBytes, start, end); + + // Create the header: [WHITELIST_CMD, total_chunks, chunk_index] + byte[] header = new byte[] { + (byte) WHITELIST_CMD, // Command ID + (byte) totalChunks, // Total number of chunks + (byte) i // Current chunk index + }; + + // Combine header and payload + ByteBuffer buffer = ByteBuffer.allocate(header.length + payloadChunk.length); + buffer.put(header); + buffer.put(payloadChunk); + + chunks.add(buffer.array()); + } + + return chunks; + } } From 3828c4f39f7247ca3fb453f663c011b3632c1ae2 Mon Sep 17 00:00:00 2001 From: cayden Date: Fri, 29 Nov 2024 21:46:48 -0500 Subject: [PATCH 11/11] microphone and notification whitelist both working --- .../EvenRealitiesG1SGC.java | 69 ++++++++++++++++--- 1 file changed, 60 insertions(+), 9 deletions(-) diff --git a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java index 23505580..a51a0c49 100644 --- a/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java +++ b/SGM_android/SmartGlassesManager/src/main/java/com/teamopensmartglasses/smartglassesmanager/smartglassescommunicators/EvenRealitiesG1SGC.java @@ -31,6 +31,10 @@ import android.os.Handler; import android.util.Log; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.util.UUID; @@ -161,17 +165,18 @@ public void onServicesDiscovered(BluetoothGatt gatt, int status) { Log.d(TAG, "Sending 0xF4 Command"); sendDataSequentially(new byte[] {(byte) 0xF4, (byte) 0x01}); + //below has odd staggered times so they don't happen in sync // Start MIC streaming - setMicEnabled(true, 300); // Enable the MIC + setMicEnabled(true, 993); // Enable the MIC //enable our AugmentOS notification key -// sendWhiteListCommand(650); + sendWhiteListCommand(2038); //start sending notifications -// startPeriodicNotifications(4200); + startPeriodicNotifications(302); //start heartbeat - startHeartbeat(12000); + startHeartbeat(411); } else { Log.e(TAG, side + " glass UART service not found"); } @@ -197,7 +202,7 @@ public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteris if (data.length > 0 && (data[0] & 0xFF) == 0xF1) { int seq = data[1] & 0xFF; // Sequence number byte[] audioData = Arrays.copyOfRange(data, 2, data.length); // Extract audio data -// Log.d(TAG, "Audio data received. Seq: " + seq + ", Data: " + Arrays.toString(audioData)); + Log.d(TAG, "Audio data received. Seq: " + seq + ", Data: " + Arrays.toString(audioData) + ", from: " + gatt.getDevice().getName()); } else { Log.d(TAG, "Received non-audio response: " + bytesToHex(data) + ", from: " + gatt.getDevice().getName()); } @@ -568,7 +573,7 @@ public void displayReferenceCardSimple(String title, String body, int lingerTime // Example notification data (replace with your actual data) // String json = createNotificationJson("com.augment.os", "QuestionAnswerer", "How much caffeine in dark chocolate?", "25 to 50 grams per piece"); - String json = createNotificationJson("com.even.test", title, "...", body); + String json = createNotificationJson("com.augment.os", title, "...", body); Log.d(TAG, "the JSON to send: " + json); List chunks = createNotificationChunks(json); // Log.d(TAG, "THE CHUNKS:"); @@ -705,7 +710,7 @@ public void run() { List chunks = getWhitelistChunks(); for (byte[] chunk : chunks) { Log.d(TAG, "Sending this chunk for white list:" + bytesToUtf8(chunk)); - sendDataSequentially(chunk, true); + sendDataSequentially(chunk, false); // Sleep for 100 milliseconds between sending each chunk try { @@ -805,7 +810,7 @@ private void sendPeriodicNotification() { // Example notification data (replace with your actual data) // String json = createNotificationJson("com.augment.os", "QuestionAnswerer", "How much caffeine in dark chocolate?", "25 to 50 grams per piece"); - String json = createNotificationJson("com.even.test", "QuestionAnswerer", "How much caffeine in dark chocolate?", "25 to 50 grams per piece"); + String json = createNotificationJson("com.augment.os", "QuestionAnswerer", "How much caffeine in dark chocolate?", "25 to 50 grams per piece"); Log.d(TAG, "the JSON to send: " + json); List chunks = createNotificationChunks(json); // Log.d(TAG, "THE CHUNKS:"); @@ -845,7 +850,9 @@ private void stopPeriodicNotifications() { private static final int WHITELIST_CMD = 0x04; // Command ID for whitelist public List getWhitelistChunks() { // Define the hardcoded whitelist JSON - String whitelistJson = "[{\"id\": \"com.augment.os\", \"name\": \"AugmentOS\"}]"; + List apps = new ArrayList<>(); + apps.add(new AppInfo("com.augment.os", "AugmentOS")); + String whitelistJson = createWhitelistJson(apps); Log.d(TAG, "Creating chunks for hardcoded whitelist: " + whitelistJson); @@ -853,6 +860,50 @@ public List getWhitelistChunks() { return createWhitelistChunks(whitelistJson); } + private String createWhitelistJson(List apps) { + JSONArray appList = new JSONArray(); + try { + // Add each app to the list + for (AppInfo app : apps) { + JSONObject appJson = new JSONObject(); + appJson.put("id", app.getId()); + appJson.put("name", app.getName()); + appList.put(appJson); + } + + JSONObject whitelistJson = new JSONObject(); + whitelistJson.put("calendar_enable", false); + whitelistJson.put("call_enable", false); + whitelistJson.put("msg_enable", false); + whitelistJson.put("ios_mail_enable", false); + + JSONObject appObject = new JSONObject(); + appObject.put("list", appList); + appObject.put("enable", true); + + whitelistJson.put("app", appObject); + + return whitelistJson.toString(); + } catch (JSONException e) { + Log.e(TAG, "Error creating whitelist JSON: " + e.getMessage()); + return "{}"; + } + } + + // Simple class to hold app info + class AppInfo { + private String id; + private String name; + + public AppInfo(String id, String name) { + this.id = id; + this.name = name; + } + + public String getId() { return id; } + public String getName() { return name; } + } + // Helper function to split JSON into chunks private List createWhitelistChunks(String json) { final int MAX_CHUNK_SIZE = 180 - 4; // Reserve space for the header