add wifi request ble characteristic

This commit is contained in:
2026-01-10 14:23:39 +00:00
parent cb6f36924f
commit 4010ba8870
6 changed files with 197 additions and 47 deletions

View File

@@ -8,6 +8,7 @@
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application

View File

@@ -25,6 +25,7 @@ public final class BleCentralClient {
void onConnected();
void onPing();
void onFeedJson(String json);
void onWifiRequest(boolean requested);
}
private static final UUID CCCD_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
@@ -46,6 +47,7 @@ public final class BleCentralClient {
private boolean scanning = false;
private BluetoothDevice lastDevice = null;
private BluetoothGatt gatt = null;
private BluetoothGattDescriptor pendingWifiCccd = null;
private boolean subscribed = false;
private long lastNotificationAtMs = 0L;
@@ -265,6 +267,7 @@ public final class BleCentralClient {
if (scanning) return;
subscribed = false;
pendingWifiCccd = null;
stopLivenessCheck();
lastNotificationAtMs = 0L;
lastPingAtMs = 0L;
@@ -347,6 +350,24 @@ public final class BleCentralClient {
return;
}
BluetoothGattCharacteristic wifiRequestTx = service.getCharacteristic(Constants.WIFI_REQUEST_TX_UUID);
if (wifiRequestTx != null) {
Log.i(Constants.TAG_BLE, "Found characteristic " + Constants.WIFI_REQUEST_TX_UUID_STR + " (WIFI_REQUEST_TX)");
Log.i(Constants.TAG_BLE, "Enabling notifications for WIFI_REQUEST_TX");
boolean wifiNotifOk = gatt.setCharacteristicNotification(wifiRequestTx, true);
Log.i(Constants.TAG_BLE, "setCharacteristicNotification(WIFI_REQUEST_TX)=" + wifiNotifOk);
BluetoothGattDescriptor wifiCccd = wifiRequestTx.getDescriptor(CCCD_UUID);
if (wifiCccd == null) {
Log.w(Constants.TAG_BLE, "Missing WIFI_REQUEST_TX CCCD (0x2902)");
} else {
wifiCccd.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
pendingWifiCccd = wifiCccd;
}
} else {
Log.w(Constants.TAG_BLE, "Missing characteristic " + Constants.WIFI_REQUEST_TX_UUID_STR + " (WIFI_REQUEST_TX)");
}
BluetoothGattCharacteristic feedTx = service.getCharacteristic(Constants.FEED_TX_UUID);
if (feedTx == null) {
closeGattInternal();
@@ -358,7 +379,7 @@ public final class BleCentralClient {
Log.i(Constants.TAG_BLE, "Enabling notifications for FEED_TX");
boolean notifOk = gatt.setCharacteristicNotification(feedTx, true);
Log.i(Constants.TAG_BLE, "setCharacteristicNotification=" + notifOk);
Log.i(Constants.TAG_BLE, "setCharacteristicNotification(FEED_TX)=" + notifOk);
BluetoothGattDescriptor cccd = feedTx.getDescriptor(CCCD_UUID);
if (cccd == null) {
@@ -368,7 +389,7 @@ public final class BleCentralClient {
}
cccd.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
boolean writeOk = gatt.writeDescriptor(cccd);
Log.i(Constants.TAG_BLE, "writeDescriptor(CCCD)=" + writeOk);
Log.i(Constants.TAG_BLE, "writeDescriptor(FEED CCCD)=" + writeOk);
if (!writeOk) {
closeGattInternal();
scheduleReconnect("CCCD write failed to start");
@@ -380,61 +401,93 @@ public final class BleCentralClient {
if (descriptor == null) return;
if (!CCCD_UUID.equals(descriptor.getUuid())) return;
Log.i(Constants.TAG_BLE, "onDescriptorWrite CCCD status=" + status);
if (status == BluetoothGatt.GATT_SUCCESS) {
BluetoothGattCharacteristic ch = descriptor.getCharacteristic();
UUID chUuid = ch != null ? ch.getUuid() : null;
Log.i(Constants.TAG_BLE, "onDescriptorWrite CCCD status=" + status + " char=" + chUuid);
if (status != BluetoothGatt.GATT_SUCCESS) {
if (Constants.FEED_TX_UUID.equals(chUuid)) {
closeGattInternal();
scheduleReconnect("CCCD write failed: " + status);
return;
}
if (Constants.WIFI_REQUEST_TX_UUID.equals(chUuid)) {
Log.w(Constants.TAG_BLE, "WIFI_REQUEST_TX CCCD write failed: " + status);
pendingWifiCccd = null;
return;
}
closeGattInternal();
scheduleReconnect("CCCD write failed: " + status);
return;
}
if (Constants.FEED_TX_UUID.equals(chUuid)) {
subscribed = true;
lastNotificationAtMs = System.currentTimeMillis();
lastPingAtMs = 0L;
startLivenessCheck();
if (callback != null) callback.onStatus("Connected", null, true);
} else {
closeGattInternal();
scheduleReconnect("CCCD write failed: " + status);
}
}
private void handleCharacteristicChanged(UUID uuid, byte[] value) {
if (!Constants.FEED_TX_UUID.equals(uuid)) return;
lastNotificationAtMs = System.currentTimeMillis();
FeedReassembler.Result result = reassembler.onNotification(value, lastNotificationAtMs);
if (result == null) {
// Diagnostic: log occasionally so we can infer the framing coming from iPhone.
if (lastNotificationAtMs - lastUnparsedLogAtMs > 1000L) {
lastUnparsedLogAtMs = lastNotificationAtMs;
String hint = "";
if (value != null && value.length >= 9) {
long msgId = ((long) (value[0] & 0xFF))
| ((long) (value[1] & 0xFF) << 8)
| ((long) (value[2] & 0xFF) << 16)
| ((long) (value[3] & 0xFF) << 24);
int type = value[4] & 0xFF;
int idx = (value[5] & 0xFF) | ((value[6] & 0xFF) << 8);
int cnt = (value[7] & 0xFF) | ((value[8] & 0xFF) << 8);
hint = " msgId=" + (msgId & 0xFFFFFFFFL) + " type=" + type + " chunk=" + idx + "/" + cnt;
if (pendingWifiCccd != null) {
boolean wifiWriteOk = gatt.writeDescriptor(pendingWifiCccd);
Log.i(Constants.TAG_BLE, "writeDescriptor(WIFI CCCD)=" + wifiWriteOk);
if (!wifiWriteOk) {
Log.w(Constants.TAG_BLE, "WIFI_REQUEST_TX CCCD write failed to start");
pendingWifiCccd = null;
}
Log.d(Constants.TAG_FEED, "Unparsed notify len=" + (value != null ? value.length : 0) + hint + " hex=" + hexPrefix(value, 16));
}
return;
}
if (result.isPing) {
Log.d(Constants.TAG_FEED, "PING");
lastPingAtMs = lastNotificationAtMs;
if (callback != null) callback.onPing();
return;
if (Constants.WIFI_REQUEST_TX_UUID.equals(chUuid)) {
Log.i(Constants.TAG_BLE, "Subscribed to WIFI_REQUEST_TX");
pendingWifiCccd = null;
}
if (result.jsonOrNull != null) {
Log.i(Constants.TAG_FEED, "Reassembled JSON (" + result.jsonOrNull.length() + " bytes)");
Log.i(Constants.TAG_FEED, "RAW_JSON_BEGIN");
logLarge(Constants.TAG_FEED, result.jsonOrNull);
Log.i(Constants.TAG_FEED, "RAW_JSON_END");
if (callback != null) callback.onFeedJson(result.jsonOrNull);
}
private void handleCharacteristicChanged(UUID uuid, byte[] value) {
if (Constants.WIFI_REQUEST_TX_UUID.equals(uuid)) {
boolean requested = value != null && value.length > 0 && (value[0] & 0xFF) != 0;
Log.i(Constants.TAG_BLE, "WIFI_REQUEST_TX notify=" + requested + " raw=" + hexPrefix(value, 1));
if (callback != null) callback.onWifiRequest(requested);
} else if (Constants.FEED_TX_UUID.equals(uuid)) {
lastNotificationAtMs = System.currentTimeMillis();
FeedReassembler.Result result = reassembler.onNotification(value, lastNotificationAtMs);
if (result == null) {
// Diagnostic: log occasionally so we can infer the framing coming from iPhone.
if (lastNotificationAtMs - lastUnparsedLogAtMs > 1000L) {
lastUnparsedLogAtMs = lastNotificationAtMs;
String hint = "";
if (value != null && value.length >= 9) {
long msgId = ((long) (value[0] & 0xFF))
| ((long) (value[1] & 0xFF) << 8)
| ((long) (value[2] & 0xFF) << 16)
| ((long) (value[3] & 0xFF) << 24);
int type = value[4] & 0xFF;
int idx = (value[5] & 0xFF) | ((value[6] & 0xFF) << 8);
int cnt = (value[7] & 0xFF) | ((value[8] & 0xFF) << 8);
hint = " msgId=" + (msgId & 0xFFFFFFFFL) + " type=" + type + " chunk=" + idx + "/" + cnt;
}
Log.d(Constants.TAG_FEED, "Unparsed notify len=" + (value != null ? value.length : 0) + hint + " hex=" + hexPrefix(value, 16));
}
} else if (result.isPing) {
Log.d(Constants.TAG_FEED, "PING");
lastPingAtMs = lastNotificationAtMs;
if (callback != null) callback.onPing();
} else if (result.jsonOrNull != null) {
Log.i(Constants.TAG_FEED, "Reassembled JSON (" + result.jsonOrNull.length() + " bytes)");
Log.i(Constants.TAG_FEED, "RAW_JSON_BEGIN");
logLarge(Constants.TAG_FEED, result.jsonOrNull);
Log.i(Constants.TAG_FEED, "RAW_JSON_END");
if (callback != null) callback.onFeedJson(result.jsonOrNull);
}
}
}
private void closeGattInternal() {
subscribed = false;
pendingWifiCccd = null;
stopLivenessCheck();
handler.removeCallbacks(connectTimeoutRunnable);
if (gatt != null) {

View File

@@ -1,8 +1,10 @@
package sh.nym.irisglass;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.wifi.WifiManager;
import android.os.HandlerThread;
import android.os.IBinder;
import android.preference.PreferenceManager;
@@ -92,6 +94,11 @@ public final class BleLinkService extends Service implements BleCentralClient.Ca
}
}
@Override
public void onWifiRequest(boolean requested) {
setWifiEnabled(requested);
}
private void maybeNudgeWinnerChanged(FeedEnvelope env) {
if (env == null) return;
@@ -122,4 +129,11 @@ public final class BleLinkService extends Service implements BleCentralClient.Ca
i.putExtra(Constants.EXTRA_WINNER_ID, winnerId);
startService(i);
}
private void setWifiEnabled(boolean enabled) {
WifiManager wifiManager = (WifiManager) this.getSystemService(Context.WIFI_SERVICE);
if (wifiManager != null) {
wifiManager.setWifiEnabled(enabled);
}
}
}

View File

@@ -12,10 +12,12 @@ public final class Constants {
public static final String SERVICE_UUID_STR = "A0B0C0D0-E0F0-4A0B-9C0D-0E0F1A2B3C4D";
public static final String FEED_TX_UUID_STR = "A0B0C0D1-E0F0-4A0B-9C0D-0E0F1A2B3C4D";
public static final String CONTROL_RX_UUID_STR = "A0B0C0D2-E0F0-4A0B-9C0D-0E0F1A2B3C4D";
public static final String WIFI_REQUEST_TX_UUID_STR = "A0B0C0D3-E0F0-4A0B-9C0D-0E0F1A2B3C4D";
public static final UUID SERVICE_UUID = UUID.fromString(SERVICE_UUID_STR);
public static final UUID FEED_TX_UUID = UUID.fromString(FEED_TX_UUID_STR);
public static final UUID CONTROL_RX_UUID = UUID.fromString(CONTROL_RX_UUID_STR);
public static final UUID WIFI_REQUEST_TX_UUID = UUID.fromString(WIFI_REQUEST_TX_UUID_STR);
public static final String PERIPHERAL_NAME_HINT = "Aris";
public static final String LEGACY_PERIPHERAL_NAME_HINT = "GlassNow";