1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-07-23 23:32:23 +02:00

Bangle.js: Add Sleep as Android support (#3785)

Reviewed-on: https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/3785
Co-authored-by: André Büsgen <andre.buesgen@posteo.de>
Co-committed-by: André Büsgen <andre.buesgen@posteo.de>
This commit is contained in:
André Büsgen 2024-06-08 12:46:09 +00:00 committed by José Rebelo
parent b7aec071ff
commit a80054157a
4 changed files with 148 additions and 6 deletions

View File

@ -31,7 +31,9 @@ import org.apache.commons.lang3.ArrayUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import nodomain.freeyourgadget.gadgetbridge.BuildConfig; import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
@ -40,6 +42,7 @@ import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpec
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.SleepAsAndroidFeature;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device; import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
@ -80,6 +83,16 @@ public class BangleJSCoordinator extends AbstractBLEDeviceCoordinator {
return true; return true;
} }
@Override
public boolean supportsSleepAsAndroid() {
return true;
}
@Override
public Set<SleepAsAndroidFeature> getSleepAsAndroidFeatures() {
return EnumSet.of(SleepAsAndroidFeature.ACCELEROMETER, SleepAsAndroidFeature.HEART_RATE, SleepAsAndroidFeature.NOTIFICATIONS, SleepAsAndroidFeature.ALARMS);
}
@Override @Override
public boolean supportsRealtimeData() { public boolean supportsRealtimeData() {
return true; return true;

View File

@ -22,6 +22,7 @@ import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothGattService;
import android.os.Bundle;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -361,6 +362,11 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
return false; return false;
} }
@Override
public void onSleepAsAndroidAction(String action, Bundle extras) {
}
@Override @Override
public boolean onCharacteristicChanged(BluetoothGatt gatt, public boolean onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) { BluetoothGattCharacteristic characteristic) {

View File

@ -47,6 +47,7 @@ import android.graphics.drawable.Drawable;
import android.location.Location; import android.location.Location;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle;
import android.util.Base64; import android.util.Base64;
import android.widget.Toast; import android.widget.Toast;
@ -76,6 +77,7 @@ import java.io.IOException;
import java.io.StringReader; import java.io.StringReader;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.sql.Timestamp;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
@ -110,9 +112,9 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallContro
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot;
import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSConstants; import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSConstants;
import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.BangleJSActivitySample; import nodomain.freeyourgadget.gadgetbridge.entities.BangleJSActivitySample;
@ -120,8 +122,9 @@ import nodomain.freeyourgadget.gadgetbridge.entities.CalendarSyncState;
import nodomain.freeyourgadget.gadgetbridge.entities.CalendarSyncStateDao; import nodomain.freeyourgadget.gadgetbridge.entities.CalendarSyncStateDao;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.externalevents.CalendarReceiver; import nodomain.freeyourgadget.gadgetbridge.externalevents.CalendarReceiver;
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationService;
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationProviderType; import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationProviderType;
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationService;
import nodomain.freeyourgadget.gadgetbridge.externalevents.sleepasandroid.SleepAsAndroidAction;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
@ -136,6 +139,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes; import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.service.SleepAsAndroidSender;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions; import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue; import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue;
@ -188,6 +192,8 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
// Global Intents // Global Intents
private static final String BANGLE_ACTION_UART_TX = "com.banglejs.uart.tx"; private static final String BANGLE_ACTION_UART_TX = "com.banglejs.uart.tx";
private SleepAsAndroidSender sleepAsAndroidSender;
public BangleJSDeviceSupport() { public BangleJSDeviceSupport() {
super(LOG); super(LOG);
addSupportedService(BangleJSConstants.UUID_SERVICE_NORDIC_UART); addSupportedService(BangleJSConstants.UUID_SERVICE_NORDIC_UART);
@ -322,6 +328,10 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
protected TransactionBuilder initializeDevice(TransactionBuilder builder) { protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
LOG.info("Initializing"); LOG.info("Initializing");
if (sleepAsAndroidSender == null) {
sleepAsAndroidSender = new SleepAsAndroidSender(gbDevice);
}
gbDevice.setState(GBDevice.State.INITIALIZING); gbDevice.setState(GBDevice.State.INITIALIZING);
gbDevice.sendDeviceUpdateIntent(getContext()); gbDevice.sendDeviceUpdateIntent(getContext());
gbDevice.setBatteryThresholdPercent((short) 15); gbDevice.setBatteryThresholdPercent((short) 15);
@ -600,12 +610,121 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
stopLocationUpdate(); stopLocationUpdate();
} }
} break; } break;
case "accel":
handleAcceleration(json);
break;
default : { default : {
LOG.info("UART RX JSON packet type '"+packetType+"' not understood."); LOG.info("UART RX JSON packet type '"+packetType+"' not understood.");
} }
} }
} }
@Override
public void onSleepAsAndroidAction(String action, Bundle extras) {
// Validate if our device can work with an action
try {
sleepAsAndroidSender.validateAction(action);
} catch (UnsupportedOperationException e) {
return;
}
// Consult the SleepAsAndroid documentation for a set of actions and their extra
// https://docs.sleep.urbandroid.org/devs/wearable_api.html
switch (action) {
case SleepAsAndroidAction.CHECK_CONNECTED:
sleepAsAndroidSender.confirmConnected();
break;
// Received when the app starts sleep tracking
case SleepAsAndroidAction.START_TRACKING:
this.enableAccelSender(true);
sleepAsAndroidSender.startTracking();
break;
// Received when the app stops sleep tracking
case SleepAsAndroidAction.STOP_TRACKING:
this.enableAccelSender(false);
sleepAsAndroidSender.stopTracking();
break;
case SleepAsAndroidAction.SET_SUSPENDED:
boolean suspended = extras.getBoolean("SUSPENDED", false);
this.enableAccelSender(false);
sleepAsAndroidSender.pauseTracking(suspended);
break;
// Received when the app changes the batch size for the movement data
case SleepAsAndroidAction.SET_BATCH_SIZE:
long batchSize = extras.getLong("SIZE", 12L);
sleepAsAndroidSender.setBatchSize(batchSize);
break;
// Received when the app sends a notificaation
case SleepAsAndroidAction.SHOW_NOTIFICATION:
NotificationSpec notificationSpec = new NotificationSpec();
notificationSpec.title = extras.getString("TITLE");
notificationSpec.body = extras.getString("BODY");
this.onNotification(notificationSpec);
break;
case SleepAsAndroidAction.UPDATE_ALARM:
long alarmTimestamp = extras.getLong("TIMESTAMP");
// Sets the alarm at a giver hour and minute
// Snoozing from the app will create a new alarm in the future
this.setSleepAsAndroidAlarm(alarmTimestamp);
break;
default:
LOG.warn("Received unsupported " + action);
break;
}
}
private void enableAccelSender(boolean enable) {
/**
* Sends an event to the Banglejs to enable/disable Acceleration tracking
* @param enable: whether to enable tracking
**/
try {
JSONObject o = new JSONObject();
o.put("t", "accelsender");
o.put("enable", enable);
o.put("interval", 1000);
uartTxJSON("enableAccelSender", o);
} catch (JSONException e) {
LOG.info("JSONException: " + e.getLocalizedMessage());
}
}
private void setSleepAsAndroidAlarm(long alarmTimestamp) {
/**
* Updates the Sleep as Android Alarm slot.
* @param alarmTimestamp: Unix timestamp of the upcoming alarm.
**/
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(new Timestamp(alarmTimestamp).getTime());
// Get Alarm in relevant slot
nodomain.freeyourgadget.gadgetbridge.entities.Alarm currentAlarm = DBHelper.getAlarms(gbDevice).get(SleepAsAndroidSender.getAlarmSlot());
currentAlarm.setRepetition(Alarm.ALARM_ONCE);
currentAlarm.setHour(calendar.get(Calendar.HOUR_OF_DAY));
currentAlarm.setMinute(calendar.get(Calendar.MINUTE));
currentAlarm.setEnabled(true);
currentAlarm.setUnused(false);
// Store modified alarm
DBHelper.store(currentAlarm);
// Send alarms to Gadgetbridge
this.onSetAlarms(new ArrayList<Alarm>(DBHelper.getAlarms(gbDevice)));
}
/**
* Handle "accel" packets: Acceleration data streaming
*/
private void handleAcceleration(JSONObject json) throws JSONException {
if (json.has("accel")) {
JSONObject accel = json.getJSONObject("accel");
sleepAsAndroidSender.onAccelChanged((float) (accel.getDouble("x") * 9.80665),
(float) (accel.getDouble("y") * 9.80665), (float) (accel.getDouble("z") * 9.80665));
}
}
/** /**
* Handle "status" packets: battery info updates * Handle "status" packets: battery info updates
*/ */
@ -714,6 +833,9 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
LOG.info("JSON activity '"+actName+"' not found"); LOG.info("JSON activity '"+actName+"' not found");
} }
}*/ }*/
if(hrm>0) {
sleepAsAndroidSender.onHrChanged(hrm, 0);
}
sample.setTimestamp(timestamp); sample.setTimestamp(timestamp);
sample.setRawKind(activity); sample.setRawKind(activity);
sample.setHeartRate(hrm); sample.setHeartRate(hrm);
@ -877,7 +999,7 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
* Handle "force_calendar_sync" packet * Handle "force_calendar_sync" packet
*/ */
private void handleCalendarSync(JSONObject json) throws JSONException { private void handleCalendarSync(JSONObject json) throws JSONException {
if(!GBApplication.getPrefs().getBoolean("enable_calendar_sync", false)) return; if (!GBApplication.getPrefs().getBoolean("enable_calendar_sync", false)) return;
//pretty much like the updateEvents in CalendarReceiver, but would need a lot of libraries here //pretty much like the updateEvents in CalendarReceiver, but would need a lot of libraries here
JSONArray ids = json.getJSONArray("ids"); JSONArray ids = json.getJSONArray("ids");
ArrayList<Long> idsList = new ArrayList<>(ids.length()); ArrayList<Long> idsList = new ArrayList<>(ids.length());
@ -1670,7 +1792,7 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
@Override @Override
public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) { public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
if(!GBApplication.getPrefs().getBoolean("enable_calendar_sync", false)) return; if (!GBApplication.getPrefs().getBoolean("enable_calendar_sync", false)) return;
String description = calendarEventSpec.description; String description = calendarEventSpec.description;
if (description != null) { if (description != null) {
// remove any HTML formatting // remove any HTML formatting
@ -1705,7 +1827,7 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
@Override @Override
public void onDeleteCalendarEvent(byte type, long id) { public void onDeleteCalendarEvent(byte type, long id) {
// FIXME: CalenderReceiver will call this directly - can we somehow batch up delete calls and use deleteCalendarEvents? // FIXME: CalenderReceiver will call this directly - can we somehow batch up delete calls and use deleteCalendarEvents?
if(!GBApplication.getPrefs().getBoolean("enable_calendar_sync", false)) return; if (!GBApplication.getPrefs().getBoolean("enable_calendar_sync", false)) return;
try { try {
JSONObject o = new JSONObject(); JSONObject o = new JSONObject();
o.put("t", "calendar-"); o.put("t", "calendar-");
@ -1718,7 +1840,7 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
/* Called when we need to get rid of multiple calendar events */ /* Called when we need to get rid of multiple calendar events */
public void deleteCalendarEvents(ArrayList<Long> ids) { public void deleteCalendarEvents(ArrayList<Long> ids) {
if(!GBApplication.getPrefs().getBoolean("enable_calendar_sync", false)) return; if (!GBApplication.getPrefs().getBoolean("enable_calendar_sync", false)) return;
if (ids.size() > 0) if (ids.size() > 0)
try { try {
JSONObject o = new JSONObject(); JSONObject o = new JSONObject();

View File

@ -917,6 +917,7 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
setRawSensor(!suspended); setRawSensor(!suspended);
enableRealtimeSamplesTimer(!suspended); enableRealtimeSamplesTimer(!suspended);
sleepAsAndroidSender.pauseTracking(suspended); sleepAsAndroidSender.pauseTracking(suspended);
break;
// Received when the app changes the batch size for the movement data // Received when the app changes the batch size for the movement data
case SleepAsAndroidAction.SET_BATCH_SIZE: case SleepAsAndroidAction.SET_BATCH_SIZE:
long batchSize = extras.getLong("SIZE", 12L); long batchSize = extras.getLong("SIZE", 12L);