1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-07-03 02:06:21 +02:00

Initial support for AsteroidOS watches

The watch does not auto-detect through pairing and therefore requires
discovering unsupported devices.
This commit is contained in:
Noodlez 2022-12-31 17:47:04 -08:00 committed by Gitea
parent f3fa01dfba
commit f7395ec6b2
10 changed files with 791 additions and 0 deletions

View File

@ -0,0 +1,42 @@
package nodomain.freeyourgadget.gadgetbridge.devices.asteroidos;
import java.util.UUID;
public class AsteroidOSConstants {
// AsteroidOS Service Watch Filter UUID
public static final UUID SERVICE_UUID = UUID.fromString("00000000-0000-0000-0000-00A57E401D05");
// Battery level
public static final UUID BATTERY_SERVICE_UUID = UUID.fromString("0000180F-0000-1000-8000-00805F9B34FB");
public static final UUID BATTERY_UUID = UUID.fromString("00002A19-0000-1000-8000-00805F9B34FB");
// Time
public static final UUID TIME_SERVICE_UUID = UUID.fromString("00005071-0000-0000-0000-00A57E401D05");
public static final UUID TIME_SET_CHAR = UUID.fromString("00005001-0000-0000-0000-00A57E401D05");
// ScreenshotService
public static final UUID SCREENSHOT_SERVICE_UUID = UUID.fromString("00006071-0000-0000-0000-00A57E401D05");
public static final UUID SCREENSHOT_REQUEST = UUID.fromString("00006001-0000-0000-0000-00A57E401D05");
public static final UUID SCREENSHOT_CONTENT = UUID.fromString("00006002-0000-0000-0000-00A57E401D05");
// MediaService
public static final UUID MEDIA_SERVICE_UUID = UUID.fromString("00007071-0000-0000-0000-00A57E401D05");
public static final UUID MEDIA_TITLE_CHAR = UUID.fromString("00007001-0000-0000-0000-00A57E401D05");
public static final UUID MEDIA_ALBUM_CHAR = UUID.fromString("00007002-0000-0000-0000-00A57E401D05");
public static final UUID MEDIA_ARTIST_CHAR = UUID.fromString("00007003-0000-0000-0000-00A57E401D05");
public static final UUID MEDIA_PLAYING_CHAR = UUID.fromString("00007004-0000-0000-0000-00A57E401D05");
public static final UUID MEDIA_COMMANDS_CHAR = UUID.fromString("00007005-0000-0000-0000-00A57E401D05");
public static final UUID MEDIA_VOLUME_CHAR = UUID.fromString("00007006-0000-0000-0000-00A57E401D05");
// WeatherService
public static final UUID WEATHER_SERVICE_UUID = UUID.fromString("00008071-0000-0000-0000-00A57E401D05");
public static final UUID WEATHER_CITY_CHAR = UUID.fromString("00008001-0000-0000-0000-00A57E401D05");
public static final UUID WEATHER_IDS_CHAR = UUID.fromString("00008002-0000-0000-0000-00A57E401D05");
public static final UUID WEATHER_MIN_TEMPS_CHAR = UUID.fromString("00008003-0000-0000-0000-00A57E401D05");
public static final UUID WEATHER_MAX_TEMPS_CHAR = UUID.fromString("00008004-0000-0000-0000-00A57E401D05");
// Notification Service
public static final UUID NOTIFICATION_SERVICE_UUID = UUID.fromString("00009071-0000-0000-0000-00A57E401D05");
public static final UUID NOTIFICATION_UPDATE_CHAR = UUID.fromString("00009001-0000-0000-0000-00A57E401D05");
public static final UUID NOTIFICATION_FEEDBACK_CHAR = UUID.fromString("00009002-0000-0000-0000-00A57E401D05");
}

View File

@ -0,0 +1,163 @@
package nodomain.freeyourgadget.gadgetbridge.devices.asteroidos;
import android.app.Activity;
import android.bluetooth.le.ScanFilter;
import android.content.Context;
import android.net.Uri;
import android.os.Parcel;
import android.os.ParcelUuid;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.asteroidos.AsteroidOSDeviceSupport;
public class AsteroidOSDeviceCoordinator extends AbstractDeviceCoordinator {
private static final Logger LOG = LoggerFactory.getLogger(AsteroidOSDeviceCoordinator.class);
@Override
public DeviceType getDeviceType() {
return DeviceType.ASTEROIDOS;
}
@Override
public String getManufacturer() {
return "AsteroidOS";
}
@Override
public boolean supportsAppsManagement() {
return false;
}
@Override
public Class<? extends Activity> getAppsManagementActivity() {
return null;
}
@NonNull
@Override
public Collection<? extends ScanFilter> createBLEScanFilters() {
ParcelUuid asteroidUUID = ParcelUuid.fromString(AsteroidOSConstants.SERVICE_UUID.toString());
ScanFilter serviceFilter = new ScanFilter.Builder().setServiceUuid(asteroidUUID).build();
List<ScanFilter> filters = new ArrayList();
filters.add(serviceFilter);
return filters;
}
@Override
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
}
@NonNull
@Override
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
if (candidate.supportsService(AsteroidOSConstants.SERVICE_UUID)) {
return DeviceType.ASTEROIDOS;
}
{
LOG.debug("Name: " + candidate.getName());
LOG.debug("Num services: " + candidate.getServiceUuids().length);
for (ParcelUuid uuid : candidate.getServiceUuids()) {
LOG.debug("Service UUID: " + uuid.toString());
}
}
return DeviceType.UNKNOWN;
}
@Override
public int getBondingStyle() {
return BONDING_STYLE_ASK;
}
@Override
public boolean supportsCalendarEvents() {
return false;
}
@Override
public boolean supportsRealtimeData() {
return false;
}
@Nullable
@Override
public Class<? extends Activity> getPairingActivity() {
return null;
}
@Override
public boolean supportsActivityDataFetching() {
return false;
}
@Override
public boolean supportsActivityTracking() {
return false;
}
@Override
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
return null;
}
@Override
public InstallHandler findInstallHandler(Uri uri, Context context) {
return null;
}
@Override
public boolean supportsWeather() {
return true;
}
@Override
public boolean supportsFindDevice() {
return false;
}
@Override
public boolean supportsScreenshots() {
return false;
}
@Override
public int getAlarmSlotCount() {
return 0;
}
@Override
public boolean supportsSmartWakeup(GBDevice device) {
return false;
}
@Override
public boolean supportsHeartRateMeasurement(GBDevice device) {
return false;
}
@Override
public boolean supportsMusicInfo() {
return true;
}
}

View File

@ -0,0 +1,41 @@
package nodomain.freeyourgadget.gadgetbridge.devices.asteroidos;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
public class AsteroidOSMediaCommand {
public static final byte COMMAND_PREVIOUS = 0x0;
public static final byte COMMAND_NEXT = 0x1;
public static final byte COMMAND_PLAY = 0x2;
public static final byte COMMAND_PAUSE = 0x3;
public static final byte COMMAND_VOLUME = 0x4;
public byte command = 0x0;
public AsteroidOSMediaCommand(byte value) {
command = value;
}
public GBDeviceEventMusicControl toMusicControlEvent() {
GBDeviceEventMusicControl event = new GBDeviceEventMusicControl();
switch (command) {
case COMMAND_PREVIOUS:
event.event = GBDeviceEventMusicControl.Event.PREVIOUS;
break;
case COMMAND_NEXT:
event.event = GBDeviceEventMusicControl.Event.NEXT;
break;
case COMMAND_PLAY:
event.event = GBDeviceEventMusicControl.Event.PLAY;
break;
case COMMAND_PAUSE:
event.event = GBDeviceEventMusicControl.Event.PAUSE;
break;
case COMMAND_VOLUME:
default:
event.event = GBDeviceEventMusicControl.Event.UNKNOWN;
}
return event;
}
}

View File

@ -0,0 +1,104 @@
package nodomain.freeyourgadget.gadgetbridge.devices.asteroidos;
import android.content.Context;
import androidx.annotation.NonNull;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
public class AsteroidOSNotification {
private String packageName = null;
private Integer id = null;
private String applicationName = null;
private String body = null;
private String summary = null;
private String icon = null;
private Boolean remove = false;
public enum VibrationStrength {
STRONG,
NORMAL,
RINGTONE,
NONE;
@NonNull
@Override
public String toString() {
switch (this) {
case STRONG:
return "strong";
case NORMAL:
return "normal";
case RINGTONE:
return "ringtone";
case NONE:
return "none";
}
return "";
}
}
private VibrationStrength vibrationStrength = VibrationStrength.NORMAL;
public AsteroidOSNotification(NotificationSpec spec) {
this.body = spec.body;
this.applicationName = spec.sourceName;
this.summary = spec.subject;
this.id = spec.getId();
this.packageName = spec.sourceAppId;
}
/**
* Creates a call notification
* @param callSpec The callSpec given by the device support
*/
public AsteroidOSNotification(CallSpec callSpec) {
switch (callSpec.command) {
case CallSpec.CALL_INCOMING:
this.applicationName = GBApplication.getContext().getString(R.string.pref_screen_notification_profile_incoming_call);
this.summary = callSpec.name;
this.body = callSpec.number;
this.vibrationStrength = VibrationStrength.RINGTONE;
this.id = (callSpec.name + callSpec.number).hashCode();
break;
default:
this.id = (callSpec.name + callSpec.number).hashCode();
this.remove = true;
break;
}
}
/**
* Creates a "remove" notification
* @param id Notification ID to remove
*/
public AsteroidOSNotification(int id) {
this.id = id;
this.remove = true;
}
@Override
public String toString() {
if (remove) {
return "<remove><id>" + this.id + "</id></remove>";
}
String retString = new String();
retString += "<insert>";
if (id != null)
retString += "<id>" + id + "</id>";
retString += "<vb>" + vibrationStrength.toString() + "</vb>";
if (packageName != null)
retString += "<pn>" + packageName + "</pn>";
if (applicationName != null)
retString += "<an>" + applicationName + "</an>";
if (icon != null)
retString += "<ai>" + icon + "</ai>";
if (summary != null)
retString += "<su>" + summary + "</su>";
if (body != null)
retString += "<bo>" + body + "</bo>";
retString += "</insert>";
return retString;
}
}

View File

@ -0,0 +1,61 @@
package nodomain.freeyourgadget.gadgetbridge.devices.asteroidos;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
public class AsteroidOSWeather {
public class Day {
public int minTemp;
public int maxTemp;
public int condition;
public Day(WeatherSpec.Forecast forecast) {
minTemp = forecast.minTemp;
maxTemp = forecast.maxTemp;
condition = forecast.conditionCode;
}
}
public Day[] days = new Day[5];
public String cityName = "";
public AsteroidOSWeather(WeatherSpec spec) {
cityName = spec.location;
for (int i = 0; i < 5 && i < spec.forecasts.size(); i++) {
days[i] = new Day(spec.forecasts.get(i));
}
}
public byte[] getCityName() {
return cityName.getBytes(StandardCharsets.UTF_8);
}
public byte[] getWeatherConditions() {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
for (Day day : days) {
stream.write((byte) (day.condition >> 8));
stream.write((byte) (day.condition));
}
return stream.toByteArray();
}
public byte[] getMinTemps() {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
for (Day day : days) {
stream.write((byte) (day.minTemp >> 8));
stream.write((byte) (day.minTemp));
}
return stream.toByteArray();
}
public byte[] getMaxTemps() {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
for (Day day : days) {
stream.write((byte) (day.maxTemp >> 8));
stream.write((byte) (day.maxTemp));
}
return stream.toByteArray();
}
}

View File

@ -127,6 +127,7 @@ public enum DeviceType {
BINARY_SENSOR(510, R.drawable.ic_device_unknown, R.drawable.ic_device_unknown_disabled, R.string.devicetype_binary_sensor),
FLIPPER_ZERO(520, R.drawable.ic_device_flipper, R.drawable.ic_device_flipper_disabled, R.string.devicetype_flipper_zero),
SUPER_CARS(530, R.drawable.ic_device_supercars, R.drawable.ic_device_supercars_disabled, R.string.devicetype_super_cars),
ASTEROIDOS(540, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_asteroidos),
TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_test);
private final int key;

View File

@ -33,6 +33,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.asteroidos.AsteroidOSDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.BinarySensorSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.fitpro.FitProDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.banglejs.BangleJSDeviceSupport;
@ -355,6 +356,8 @@ public class DeviceSupportFactory {
return new ServiceDeviceSupport(new FlipperZeroSupport());
case SUPER_CARS:
return new ServiceDeviceSupport(new SuperCarsSupport());
case ASTEROIDOS:
return new ServiceDeviceSupport(new AsteroidOSDeviceSupport());
}
return null;
}

View File

@ -0,0 +1,373 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.asteroidos;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
import nodomain.freeyourgadget.gadgetbridge.devices.asteroidos.AsteroidOSConstants;
import nodomain.freeyourgadget.gadgetbridge.devices.asteroidos.AsteroidOSMediaCommand;
import nodomain.freeyourgadget.gadgetbridge.devices.asteroidos.AsteroidOSNotification;
import nodomain.freeyourgadget.gadgetbridge.devices.asteroidos.AsteroidOSWeather;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfoProfile;
public class AsteroidOSDeviceSupport extends AbstractBTLEDeviceSupport {
private static final Logger LOG = LoggerFactory.getLogger(AsteroidOSDeviceSupport.class);
private final BatteryInfoProfile<AsteroidOSDeviceSupport> batteryInfoProfile;
private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
public AsteroidOSDeviceSupport() {
super(LOG);
addSupportedService(AsteroidOSConstants.SERVICE_UUID);
addSupportedService(AsteroidOSConstants.TIME_SERVICE_UUID);
addSupportedService(GattService.UUID_SERVICE_BATTERY_SERVICE);
addSupportedService(AsteroidOSConstants.WEATHER_SERVICE_UUID);
addSupportedService(AsteroidOSConstants.NOTIFICATION_SERVICE_UUID);
addSupportedService(AsteroidOSConstants.MEDIA_SERVICE_UUID);
IntentListener mListener = new IntentListener() {
@Override
public void notify(Intent intent) {
String action = intent.getAction();
if (BatteryInfoProfile.ACTION_BATTERY_INFO.equals(action)) {
handleBatteryInfo((nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfo) intent.getParcelableExtra(BatteryInfoProfile.EXTRA_BATTERY_INFO));
}
}
};
batteryInfoProfile = new BatteryInfoProfile<>(this);
batteryInfoProfile.addListener(mListener);
addSupportedProfile(batteryInfoProfile);
}
private void handleBatteryInfo(nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfo info) {
batteryCmd.level = info.getPercentCharged();
handleGBDeviceEvent(batteryCmd);
}
public boolean onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
UUID characteristicUUID = characteristic.getUuid();
byte[] value = characteristic.getValue();
if (characteristicUUID.equals(AsteroidOSConstants.MEDIA_COMMANDS_CHAR)) {
handleMediaCommand(gatt, characteristic);
return true;
}
LOG.info("Characteristic changed UUID: " + characteristicUUID);
LOG.info("Characteristic changed value: " + characteristic.getValue());
return false;
}
@Override
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
getDevice().setFirmwareVersion("N/A");
getDevice().setFirmwareVersion2("N/A");
builder.notify(getCharacteristic(AsteroidOSConstants.MEDIA_COMMANDS_CHAR), true);
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
batteryInfoProfile.requestBatteryInfo(builder);
batteryInfoProfile.enableNotify(builder, true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
builder.requestMtu(256);
}
return builder;
}
@Override
public void onNotification(NotificationSpec notificationSpec) {
AsteroidOSNotification notif = new AsteroidOSNotification(notificationSpec);
TransactionBuilder builder = new TransactionBuilder("send notification");
safeWriteToCharacteristic(builder, AsteroidOSConstants.NOTIFICATION_UPDATE_CHAR, notif.toString().getBytes(StandardCharsets.UTF_8));
builder.queue(getQueue());
}
@Override
public void onDeleteNotification(int id) {
AsteroidOSNotification notif = new AsteroidOSNotification(id);
TransactionBuilder builder = new TransactionBuilder("delete notification");
safeWriteToCharacteristic(builder, AsteroidOSConstants.NOTIFICATION_UPDATE_CHAR, notif.toString().getBytes(StandardCharsets.UTF_8));
builder.queue(getQueue());
}
@Override
public void onSetTime() {
GregorianCalendar now = BLETypeConversions.createCalendar();
Date nowTime = now.getTime();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write((byte) nowTime.getYear());
baos.write((byte) nowTime.getMonth());
baos.write((byte) nowTime.getDay());
baos.write((byte) nowTime.getHours());
baos.write((byte) nowTime.getMinutes());
baos.write((byte) nowTime.getSeconds());
TransactionBuilder builder = new TransactionBuilder("set time");
safeWriteToCharacteristic(builder, AsteroidOSConstants.TIME_SET_CHAR, baos.toByteArray());
builder.queue(getQueue());
}
@Override
public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
}
@Override
public void onSetCallState(CallSpec callSpec) {
AsteroidOSNotification call = new AsteroidOSNotification(callSpec);
TransactionBuilder builder = new TransactionBuilder("send call");
safeWriteToCharacteristic(builder, AsteroidOSConstants.NOTIFICATION_UPDATE_CHAR, call.toString().getBytes(StandardCharsets.UTF_8));
builder.queue(getQueue());
}
@Override
public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
}
@Override
public void onSetMusicState(MusicStateSpec stateSpec) {
TransactionBuilder builder = new TransactionBuilder("set music state");
if (stateSpec.state == MusicStateSpec.STATE_PLAYING) {
safeWriteToCharacteristic(builder, AsteroidOSConstants.MEDIA_PLAYING_CHAR, new byte[]{1});
} else {
safeWriteToCharacteristic(builder, AsteroidOSConstants.MEDIA_PLAYING_CHAR, new byte[]{0});
}
builder.queue(getQueue());
}
@Override
public void onSetMusicInfo(MusicSpec musicSpec) {
// Send title
{
TransactionBuilder builder = new TransactionBuilder("set music title");
safeWriteToCharacteristic(builder, AsteroidOSConstants.MEDIA_TITLE_CHAR, musicSpec.track.getBytes(StandardCharsets.UTF_8));
builder.queue(getQueue());
}
// Send album
{
TransactionBuilder builder = new TransactionBuilder("set music album");
safeWriteToCharacteristic(builder, AsteroidOSConstants.MEDIA_ALBUM_CHAR, musicSpec.album.getBytes(StandardCharsets.UTF_8));
builder.queue(getQueue());
}
// Send artist
{
TransactionBuilder builder = new TransactionBuilder("set music artist");
safeWriteToCharacteristic(builder, AsteroidOSConstants.MEDIA_ARTIST_CHAR, musicSpec.artist.getBytes(StandardCharsets.UTF_8));
builder.queue(getQueue());
}
}
@Override
public void onEnableRealtimeSteps(boolean enable) {
}
@Override
public void onInstallApp(Uri uri) {
}
@Override
public void onAppInfoReq() {
}
@Override
public void onAppStart(UUID uuid, boolean start) {
}
@Override
public void onAppDelete(UUID uuid) {
}
@Override
public void onAppConfiguration(UUID appUuid, String config, Integer id) {
}
@Override
public void onAppReorder(UUID[] uuids) {
}
@Override
public void onFetchRecordedData(int dataTypes) {
}
@Override
public void onReset(int flags) {
}
@Override
public void onHeartRateTest() {
}
@Override
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
}
@Override
public void onFindDevice(boolean start) {
}
@Override
public void onSetConstantVibration(int integer) {
}
@Override
public void onScreenshotReq() {
}
@Override
public void onEnableHeartRateSleepSupport(boolean enable) {
}
@Override
public void onSetHeartRateMeasurementInterval(int seconds) {
}
@Override
public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
}
@Override
public void onDeleteCalendarEvent(byte type, long id) {
}
@Override
public void onSendConfiguration(String config) {
}
@Override
public void onReadConfiguration(String config) {
}
@Override
public void onTestNewFunction() {
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
AsteroidOSWeather asteroidOSWeather = new AsteroidOSWeather(weatherSpec);
// Send weather city
{
TransactionBuilder builder = new TransactionBuilder("send weather city");
safeWriteToCharacteristic(builder, AsteroidOSConstants.WEATHER_CITY_CHAR, asteroidOSWeather.getCityName());
builder.queue(getQueue());
}
// Send weather conditions
{
TransactionBuilder builder = new TransactionBuilder("send weather conditions");
safeWriteToCharacteristic(builder, AsteroidOSConstants.WEATHER_IDS_CHAR, asteroidOSWeather.getWeatherConditions());
builder.queue(getQueue());
}
// Send min temps
{
TransactionBuilder builder = new TransactionBuilder("send weather min temps");
safeWriteToCharacteristic(builder, AsteroidOSConstants.WEATHER_MIN_TEMPS_CHAR, asteroidOSWeather.getMinTemps());
builder.queue(getQueue());
}
// Send max temps
{
TransactionBuilder builder = new TransactionBuilder("send weather max temps");
safeWriteToCharacteristic(builder, AsteroidOSConstants.WEATHER_MAX_TEMPS_CHAR, asteroidOSWeather.getMaxTemps());
builder.queue(getQueue());
}
}
@Override
public boolean useAutoConnect() {
return false;
}
/**
* This will check if the characteristic exists and can be written
* <p>
* Keeps backwards compatibility with firmware that can't take all the information
*/
private void safeWriteToCharacteristic(TransactionBuilder builder, UUID uuid, byte[] data) {
BluetoothGattCharacteristic characteristic = getCharacteristic(uuid);
if (characteristic != null &&
(characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) {
builder.write(characteristic, data);
} else {
LOG.warn("Tried to write to a characteristic that did not exist or was not writable!");
}
}
@Override
public boolean onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
if (super.onCharacteristicRead(gatt, characteristic, status)) {
return true;
}
UUID characteristicUUID = characteristic.getUuid();
LOG.info("Unhandled characteristic read: " + characteristicUUID);
return false;
}
public void handleMediaCommand (BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
LOG.info("handle media command");
AsteroidOSMediaCommand command = new AsteroidOSMediaCommand(characteristic.getValue()[0]);
GBDeviceEventMusicControl event = command.toMusicControlEvent();
evaluateGBDeviceEvent(event);
}
}

View File

@ -43,6 +43,7 @@ import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.asteroidos.AsteroidOSDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts4.AmazfitGTS4Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts4mini.AmazfitGTS4MiniCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyLinkBudsSCoordinator;
@ -359,6 +360,7 @@ public class DeviceHelper {
result.add(new BinarySensorCoordinator());
result.add(new FlipperZeroCoordinator());
result.add(new SuperCarsCoordinator());
result.add(new AsteroidOSDeviceCoordinator());
return result;
}

View File

@ -2001,4 +2001,5 @@
<string name="supercars_turbo_speed_label">Turbo Speed</string>
<string name="supercars_lights_label">Lights</string>
<string name="supercars_lights_blinking_label">Blinking</string>
<string name="devicetype_asteroidos">AsteroidOS</string>
</resources>