Last active
June 9, 2025 11:34
-
-
Save akshayaurora/8d7eec16acb15db31098b28ddea1ee65 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ScanResultCallback.java | |
| ``` | |
| package com.example.ble; | |
| public interface ScanResultCallback { | |
| void onKeywordFound(String data); | |
| } | |
| ``` | |
| BleScannerBridge.java | |
| ``` | |
| package com.example.ble; | |
| import android.bluetooth.BluetoothAdapter; | |
| import android.bluetooth.BluetoothManager; | |
| import android.bluetooth.le.*; | |
| import android.content.Context; | |
| import android.os.Handler; | |
| import android.util.Log; | |
| import java.nio.charset.StandardCharsets; | |
| public class BleScannerBridge { | |
| private final Context context; | |
| private final BluetoothLeScanner bleScanner; | |
| private ScanCallback scanCallback; | |
| private Handler handler = new Handler(); | |
| private boolean scanning = false; | |
| private ScanResultCallback callback; | |
| private String keyword = "waverian"; // default fallback | |
| private static final String TAG = "BleScannerBridge"; | |
| private static final long SCAN_PERIOD = 50000; | |
| public BleScannerBridge(Context ctx) { | |
| this.context = ctx.getApplicationContext(); | |
| BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE); | |
| BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter(); | |
| this.bleScanner = bluetoothAdapter.getBluetoothLeScanner(); | |
| } | |
| public void setCallback(ScanResultCallback cb) { | |
| this.callback = cb; | |
| } | |
| public void startScan(String keyword) { | |
| if (scanning || bleScanner == null) return; | |
| this.keyword = keyword; | |
| scanCallback = new ScanCallback() { | |
| @Override | |
| public void onScanResult(int callbackType, ScanResult result) { | |
| ScanRecord scanRecord = result.getScanRecord(); | |
| if (scanRecord != null) { | |
| String deviceName = scanRecord.getDeviceName(); // May be null | |
| String address = result.getDevice().getAddress(); | |
| int rssi = result.getRssi(); | |
| byte[] manuData = scanRecord.getManufacturerSpecificData(0xFFFF); | |
| String manuDataStr = (manuData != null) | |
| ? new String(manuData, StandardCharsets.UTF_8) | |
| : ""; | |
| String payload = "Name: " + (deviceName != null ? deviceName : "null") + | |
| ", Address: " + address + | |
| ", RSSI: " + rssi + | |
| ", ManufacturerData[0xFFFF]: " + manuDataStr; | |
| Log.d(TAG, "Scan payload: " + payload); | |
| if (manuDataStr.contains(BleScannerBridge.this.keyword)) { | |
| Log.d(TAG, "Keyword match found in manufacturer data: " + manuDataStr); | |
| if (callback != null) { | |
| callback.onKeywordFound(payload); | |
| } | |
| } | |
| } | |
| } | |
| }; | |
| bleScanner.startScan(scanCallback); | |
| scanning = true; | |
| handler.postDelayed(() -> { | |
| bleScanner.stopScan(scanCallback); | |
| scanning = false; | |
| }, SCAN_PERIOD); | |
| } | |
| public void stopScan() { | |
| if (scanning && scanCallback != null) { | |
| bleScanner.stopScan(scanCallback); | |
| scanning = false; | |
| scanCallback = null; | |
| } | |
| } | |
| } | |
| ``` | |
| Pit the java files above to a directory. | |
| In buildozer.spec use add_src | |
| ``` | |
| add_src = path/to/java/files_folder/ | |
| ``` | |
| Python | |
| ``` | |
| from kivy.app import App | |
| from jnius import autoclass, PythonJavaClass, java_method | |
| from android import mActivity | |
| app = App.get_running_app() | |
| # Load Java classes | |
| BleScannerBridge = autoclass('com.example.ble.BleScannerBridge') | |
| Runnable = autoclass('java.lang.Runnable') | |
| # Define the callback interface | |
| class MyScanCallback(PythonJavaClass): | |
| __javainterfaces__ = ['com/example/ble/ScanResultCallback'] | |
| __javacontext__ = 'app' | |
| @java_method('(Ljava/lang/String;)V') | |
| def onKeywordFound(self, data): | |
| Logger.debug(f"[BLE] Keyword match found: {data}") | |
| data = string_to_dict(data) | |
| Clock.schedule_once( | |
| lambda dt: app.discovered_receivers.update( | |
| {data['Name']: [data['Address'], None]}), 1) | |
| class StartBleScanRunnable(PythonJavaClass): | |
| __javainterfaces__ = ['java/lang/Runnable'] | |
| __javacontext__ = 'app' | |
| def __init__(self, keyword): | |
| super().__init__() | |
| self.keyword = keyword | |
| app._ble_scan_callback = MyScanCallback() | |
| @java_method('()V') # ← this is mandatory | |
| def run(self): | |
| print("[BLE] Running scan on UI thread with keyword:", self.keyword) | |
| if not hasattr(app, '_ble_bridge'): | |
| scanner = BleScannerBridge(mActivity) | |
| scanner.setCallback(app._ble_scan_callback) | |
| app._ble_bridge = scanner | |
| scanner.startScan(self.keyword) | |
| class StopBleScanRunnable(PythonJavaClass): | |
| __javainterfaces__ = ['java/lang/Runnable'] | |
| __javacontext__ = 'app' | |
| @java_method('()V') | |
| def run(self): | |
| print("[BLE] Running stop scan on UI thread") | |
| app = App.get_running_app() | |
| if hasattr(app, '_ble_bridge'): | |
| app._ble_bridge.stopScan() | |
| print("[BLE] Scan stopped") | |
| del app._ble_bridge | |
| # Start scan with a custom keyword (must be Java String) | |
| JavaString = autoclass('java.lang.String') | |
| # REPLACE <MyDeviceManufacturerData> with what you are searching for. | |
| keyword = JavaString("MyDeviceManufacturerData") | |
| # ENable Bluetooth | |
| # Give permission to scan | |
| from android.permissions import request_permissions, Permission | |
| request_permissions([Permission.ACCESS_FINE_LOCATION, Permission.BLUETOOTH_SCAN]) | |
| # Start scan | |
| mActivity.runOnUiThread(StartBleScanRunnable(keyword)) | |
| ``` |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment