diff --git a/README.md b/README.md
index 46b99416a..9146b42ac 100644
--- a/README.md
+++ b/README.md
@@ -69,6 +69,7 @@ vendor's servers.
- Pebble
- [Pebble, Steel, Time, Time Steel, Time Round, 2](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Pebble)
- PineTime (InfiniTime Firmware)
+- [SMA](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/SMA) Q2 (SMA-Q2-OSS Firmware)
- Teclast H10, H30
- TLW64
- Vibratissimo (Experimental)
diff --git a/app/build.gradle b/app/build.gradle
index 24eae5afa..b6ef69c96 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,6 +1,7 @@
apply plugin: "com.android.application"
apply plugin: "com.github.spotbugs"
apply plugin: "pmd"
+apply plugin: 'com.google.protobuf'
def ABORT_ON_CHECK_FAILURE = false
@@ -29,6 +30,7 @@ android {
versionName "0.58.2"
versionCode 198
vectorDrawables.useSupportLibrary = true
+ multiDexEnabled true
}
buildTypes {
release {
@@ -98,7 +100,8 @@ dependencies {
implementation 'com.jaredrummler:colorpicker:1.0.2'
// implementation project(":DaoCore")
implementation 'com.github.wax911:android-emojify:0.1.7'
-
+ implementation 'com.google.protobuf:protobuf-lite:3.0.0'
+ implementation "androidx.multidex:multidex:2.0.1"
}
preBuild.dependsOn(":GBDaoGenerator:genSources")
@@ -153,7 +156,7 @@ task pmd(type: Pmd) {
// this is just for spotbugs to let the plugin create the task
sourceSets {
main {
- java.srcDirs = []
+ main.java.srcDirs += "${protobuf.generatedFilesBaseDir}/main/javalite"
}
}
@@ -179,3 +182,24 @@ tasks.withType(com.github.spotbugs.SpotBugsTask) {
}
}
}
+
+protobuf {
+ protoc {
+ artifact = 'com.google.protobuf:protoc:3.0.0'
+ }
+ plugins {
+ javalite {
+ artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'
+ }
+ }
+ generateProtoTasks {
+ all().each { task ->
+ task.builtins {
+ remove java
+ }
+ task.plugins {
+ javalite { }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java
index 93d147dd8..b4a06dc25 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java
@@ -91,6 +91,8 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceType.fromKey;
import static nodomain.freeyourgadget.gadgetbridge.util.GB.NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID;
import static nodomain.freeyourgadget.gadgetbridge.util.GB.NOTIFICATION_ID_ERROR;
+import androidx.multidex.MultiDex;
+
/**
* Main Application class that initializes and provides access to certain things like
* logging and DB access.
@@ -152,6 +154,12 @@ public class GBApplication extends Application {
// don't do anything here, add it to onCreate instead
}
+ @Override
+ protected void attachBaseContext(Context base) {
+ super.attachBaseContext(base);
+ MultiDex.install(this);
+ }
+
public static Logging getLogging() {
return logging;
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/smaq2oss/SMAQ2OSSConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/smaq2oss/SMAQ2OSSConstants.java
new file mode 100644
index 000000000..f6ee28fb9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/smaq2oss/SMAQ2OSSConstants.java
@@ -0,0 +1,44 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.smaq2oss;
+
+import java.util.UUID;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport.BASE_UUID;
+
+
+public class SMAQ2OSSConstants {
+
+ // Nordic UART Service UUID
+// public static final UUID UUID_SERVICE_SMAQ2OSS = UUID.fromString("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");
+// public static final UUID UUID_CHARACTERISTIC_WRITE_NORMAL = UUID.fromString("6E400002-B5A3-F393-E0A9-E50E24DCCA9E");
+// public static final UUID UUID_CHARACTERISTIC_NOTIFY_NORMAL = UUID.fromString("6E400003-B5A3-F393-E0A9-E50E24DCCA9E");
+ // SMA-Q2-OSS watch service UUID 51be0000-c182-4f3a-9359-21337bce51f6
+ public static final UUID UUID_SERVICE_SMAQ2OSS = UUID.fromString("51be0001-c182-4f3a-9359-21337bce51f6");
+ public static final UUID UUID_CHARACTERISTIC_WRITE_NORMAL = UUID.fromString("51be0002-c182-4f3a-9359-21337bce51f6");
+ public static final UUID UUID_CHARACTERISTIC_NOTIFY_NORMAL = UUID.fromString("51be0003-c182-4f3a-9359-21337bce51f6");
+
+ public static final byte MSG_SET_TIME = 0x01;
+ public static final byte MSG_BATTERY_STATE = 0x02;
+ public static final byte MSG_MUSIC_EVENT = 0x03;
+ public static final byte MSG_SET_WEATHER = 0x04;
+ public static final byte MSG_SET_MUSIC_INFO = 0x05;
+ public static final byte MSG_CALL_NOTIFICATION = 0x06;
+ public static final byte MSG_CALL_COMMAND = 0x07;
+ public static final byte MSG_NOTIFICATION = 0x08;
+
+ public static final byte EVT_PLAY_PAUSE = 0x00;
+ public static final byte EVT_FWD = 0x01;
+ public static final byte EVT_REV = 0x02;
+ public static final byte EVT_VOL_UP = 0x03;
+ public static final byte EVT_VOL_DOWN = 0x04;
+
+ public static final int MUSIC_ARTIST_MAX_LEN = 32;
+ public static final int MUSIC_ALBUM_MAX_LEN = 32;
+ public static final int MUSIC_TRACK_MAX_LEN = 64;
+
+ public static final int CALL_NAME_MAX_LEN = 32;
+ public static final int CALL_NUMBER_MAX_LEN = 16;
+
+ public static final int NOTIFICATION_SENDER_MAX_LEN = 32;
+ public static final int NOTIFICATION_SUBJECT_MAX_LEN = 32;
+ public static final int NOTIFICATION_BODY_MAX_LEN = 200;
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/smaq2oss/SMAQ2OSSCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/smaq2oss/SMAQ2OSSCoordinator.java
new file mode 100644
index 000000000..e18a70dad
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/smaq2oss/SMAQ2OSSCoordinator.java
@@ -0,0 +1,160 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.smaq2oss;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.le.ScanFilter;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Build;
+import android.os.ParcelUuid;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Collection;
+import java.util.Collections;
+
+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;
+
+public class SMAQ2OSSCoordinator extends AbstractDeviceCoordinator {
+ private static final Logger LOG = LoggerFactory.getLogger(SMAQ2OSSCoordinator.class);
+
+ @NonNull
+ @Override
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public Collection extends ScanFilter> createBLEScanFilters() {
+ ParcelUuid service = new ParcelUuid(SMAQ2OSSConstants.UUID_SERVICE_SMAQ2OSS);
+ ScanFilter filter = new ScanFilter.Builder().setServiceUuid(service).build();
+ return Collections.singletonList(filter);
+ }
+
+ @Override
+ protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+ }
+
+ @NonNull
+ @Override
+ public DeviceType getSupportedType(GBDeviceCandidate candidate) {
+ try {
+ BluetoothDevice device = candidate.getDevice();
+ String name = device.getName();
+ // TODO still match for "SMA-Q2-OSS" because of backward firmware compatibility - remove eventually
+ if (name != null && (name.startsWith("SMAQ2-") || name.equalsIgnoreCase("SMA-Q2-OSS"))) {
+ return DeviceType.SMAQ2OSS;
+ }
+ } catch (Exception ex) {
+ LOG.error("unable to check device support", ex);
+ }
+ return DeviceType.UNKNOWN;
+ }
+
+ @Override
+ public int getBondingStyle(){
+ return BONDING_STYLE_NONE;
+ }
+
+ @Override
+ public DeviceType getDeviceType() {
+ return DeviceType.SMAQ2OSS;
+ }
+
+ @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 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 String getManufacturer() {
+ return "SMA";
+ }
+
+ @Override
+ public boolean supportsAppsManagement() {
+ return false;
+ }
+
+ @Override
+ public Class extends Activity> getAppsManagementActivity() {
+ return null;
+ }
+
+ @Override
+ public boolean supportsCalendarEvents() {
+ return false;
+ }
+
+ @Override
+ public boolean supportsRealtimeData() {
+ return false;
+ }
+
+ @Override
+ public boolean supportsWeather() {
+ return true;
+ }
+
+ @Override
+ public boolean supportsFindDevice() {
+ return true;
+ }
+
+ @Override
+ public boolean supportsMusicInfo() {
+ return true;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/smaq2oss/SMAQ2OSSSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/smaq2oss/SMAQ2OSSSupport.java
new file mode 100644
index 000000000..32ccc9485
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/smaq2oss/SMAQ2OSSSupport.java
@@ -0,0 +1,448 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.smaq2oss;
+
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.UUID;
+import java.nio.ByteBuffer;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
+import nodomain.freeyourgadget.gadgetbridge.devices.smaq2oss.SMAQ2OSSConstants;
+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.Weather;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+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.SMAQ2OSSProtos;
+import nodomain.freeyourgadget.gadgetbridge.util.NotificationUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+public class SMAQ2OSSSupport extends AbstractBTLEDeviceSupport {
+ private static final Logger LOG = LoggerFactory.getLogger(SMAQ2OSSSupport.class);
+
+ public BluetoothGattCharacteristic normalWriteCharacteristic = null;
+
+ public SMAQ2OSSSupport() {
+ super(LOG);
+ addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
+ addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE);
+ addSupportedService(SMAQ2OSSConstants.UUID_SERVICE_SMAQ2OSS);
+ }
+
+ @Override
+ protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+ normalWriteCharacteristic = getCharacteristic(SMAQ2OSSConstants.UUID_CHARACTERISTIC_WRITE_NORMAL);
+ normalWriteCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
+
+ builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+
+ setTime(builder)
+ .setInitialized(builder);
+
+ getDevice().setFirmwareVersion("N/A");
+ getDevice().setFirmwareVersion2("N/A");
+
+ builder.notify(getCharacteristic(SMAQ2OSSConstants.UUID_CHARACTERISTIC_NOTIFY_NORMAL), true);
+
+ return builder;
+ }
+
+ @Override
+ public boolean onCharacteristicChanged(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic) {
+ super.onCharacteristicChanged(gatt, characteristic);
+
+ UUID characteristicUUID = characteristic.getUuid();
+ if (SMAQ2OSSConstants.UUID_CHARACTERISTIC_NOTIFY_NORMAL.equals(characteristicUUID)) {
+ handleDeviceEvent(characteristic.getValue());
+ }
+ return true;
+ }
+
+ private void handleDeviceEvent(byte[] value){
+ if (value == null || value.length == 0) {
+ return;
+ }
+
+ switch (value[0]) {
+ case SMAQ2OSSConstants.MSG_MUSIC_EVENT:
+ LOG.info("got music control");
+ handleMusicEvent(value[1]);
+ break;
+ case SMAQ2OSSConstants.MSG_CALL_COMMAND:
+ LOG.info("got call control");
+ handleCallCommand(value[1]);
+ break;
+
+ }
+
+ }
+
+ private void handleMusicEvent(byte value){
+ GBDeviceEventMusicControl deviceEventMusicControl = new GBDeviceEventMusicControl();
+
+ switch (value){
+ case SMAQ2OSSConstants.EVT_PLAY_PAUSE:
+ deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PLAYPAUSE;
+ break;
+ case SMAQ2OSSConstants.EVT_FWD:
+ deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.NEXT;
+ break;
+ case SMAQ2OSSConstants.EVT_REV:
+ deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PREVIOUS;
+ break;
+ case SMAQ2OSSConstants.EVT_VOL_UP:
+ deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.VOLUMEUP;
+ break;
+ case SMAQ2OSSConstants.EVT_VOL_DOWN:
+ deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.VOLUMEDOWN;
+ break;
+ }
+ evaluateGBDeviceEvent(deviceEventMusicControl);
+
+ }
+
+ private void handleCallCommand(byte command){
+ GBDeviceEventCallControl callCmd = new GBDeviceEventCallControl();
+
+ switch (command){
+ case CallSpec.CALL_ACCEPT:
+ callCmd.event = GBDeviceEventCallControl.Event.ACCEPT;
+ evaluateGBDeviceEvent(callCmd);
+ break;
+ case CallSpec.CALL_REJECT:
+ callCmd.event = GBDeviceEventCallControl.Event.REJECT;
+ evaluateGBDeviceEvent(callCmd);
+ break;
+ }
+
+ }
+
+ @Override
+ public boolean useAutoConnect() {
+ return true;
+ }
+
+ @Override
+ public void onNotification(NotificationSpec notificationSpec) {
+
+ SMAQ2OSSProtos.MessageNotification.Builder notification = SMAQ2OSSProtos.MessageNotification.newBuilder();
+
+
+ notification.setTimestamp(getTimestamp());
+ String sender = StringUtils.getFirstOf(StringUtils.getFirstOf(notificationSpec.sender,notificationSpec.phoneNumber),notificationSpec.title);
+ notification.setSender(truncateUTF8(sender,SMAQ2OSSConstants.NOTIFICATION_SENDER_MAX_LEN));
+// notification.setSubject(truncateUTF8(notificationSpec.subject,SMAQ2OSSConstants.NOTIFICATION_SUBJECT_MAX_LEN));
+ notification.setBody(truncateUTF8(notificationSpec.body,SMAQ2OSSConstants.NOTIFICATION_BODY_MAX_LEN));
+
+
+ try {
+ TransactionBuilder builder;
+ builder = performInitialized("Sending notification");
+
+ builder.write(normalWriteCharacteristic,createMessage(SMAQ2OSSConstants.MSG_NOTIFICATION,notification.build().toByteArray()));
+
+ builder.queue(getQueue());
+ } catch (Exception ex) {
+ LOG.error("Error sending notification", ex);
+ }
+
+
+
+ }
+
+ @Override
+ public void onDeleteNotification(int id) {
+
+ }
+
+ @Override
+ public void onSetTime() {
+ try {
+ TransactionBuilder builder = performInitialized("time");
+ setTime(builder);
+ performConnected(builder.getTransaction());
+ } catch(IOException e) {
+ }
+ }
+
+ @Override
+ public void onSetAlarms(ArrayList extends Alarm> alarms) {
+
+ }
+
+ @Override
+ public void onSetCallState(CallSpec callSpec) {
+
+ SMAQ2OSSProtos.CallNotification.Builder callnotif = SMAQ2OSSProtos.CallNotification.newBuilder();
+
+ callnotif.setName(truncateUTF8(callSpec.name,SMAQ2OSSConstants.CALL_NAME_MAX_LEN));
+ callnotif.setNumber(truncateUTF8(callSpec.number,SMAQ2OSSConstants.CALL_NUMBER_MAX_LEN));
+ callnotif.setCommand(callSpec.command);
+
+ try {
+ TransactionBuilder builder;
+ builder = performInitialized("Sending call state");
+
+ builder.write(normalWriteCharacteristic,createMessage(SMAQ2OSSConstants.MSG_CALL_NOTIFICATION,callnotif.build().toByteArray()));
+
+ builder.queue(getQueue());
+ } catch (Exception ex) {
+ LOG.error("Error sending call state", ex);
+ }
+
+ }
+
+ @Override
+ public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
+
+ }
+
+ @Override
+ public void onSetMusicState(MusicStateSpec stateSpec) {
+
+ }
+
+ @Override
+ public void onSetMusicInfo(MusicSpec musicSpec) {
+
+
+ SMAQ2OSSProtos.MusicInfo.Builder musicInfo = SMAQ2OSSProtos.MusicInfo.newBuilder();
+
+ musicInfo.setArtist(truncateUTF8(musicSpec.artist,SMAQ2OSSConstants.MUSIC_ARTIST_MAX_LEN));
+ musicInfo.setAlbum(truncateUTF8(musicSpec.album,SMAQ2OSSConstants.MUSIC_ALBUM_MAX_LEN));
+ musicInfo.setTrack(truncateUTF8(musicSpec.track,SMAQ2OSSConstants.MUSIC_TRACK_MAX_LEN));
+
+ try {
+ TransactionBuilder builder;
+ builder = performInitialized("Sending music info");
+ LOG.info(musicInfo.getArtist());
+ LOG.info(musicInfo.getAlbum());
+ LOG.info(musicInfo.getTrack());
+
+ builder.write(normalWriteCharacteristic,createMessage(SMAQ2OSSConstants.MSG_SET_MUSIC_INFO,musicInfo.build().toByteArray()));
+
+ builder.queue(getQueue());
+ } catch (Exception ex) {
+ LOG.error("Error sending music info", ex);
+ }
+ }
+
+ @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) {
+// try {
+// getQueue().clear();
+//
+// TransactionBuilder builder = performInitialized("reboot");
+// builder.write(normalWriteCharacteristic, new byte[] {
+// SMAQ2OSSConstants.CMD_ID_DEVICE_RESTART, SMAQ2OSSConstants.CMD_KEY_REBOOT
+// });
+// performConnected(builder.getTransaction());
+// } catch(Exception e) {
+// }
+ }
+
+ @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) {
+ try {
+ TransactionBuilder builder;
+ builder = performInitialized("Sending current weather");
+
+ SMAQ2OSSProtos.SetWeather.Builder setWeather= SMAQ2OSSProtos.SetWeather.newBuilder();
+
+ setWeather.setTimestamp(weatherSpec.timestamp);
+ setWeather.setCondition(weatherSpec.currentConditionCode);
+ setWeather.setTemperature(weatherSpec.currentTemp-273);
+ setWeather.setTemperatureMin(weatherSpec.todayMinTemp-273);
+ setWeather.setTemperatureMax(weatherSpec.todayMaxTemp-273);
+ setWeather.setHumidity(weatherSpec.currentHumidity);
+
+ for (WeatherSpec.Forecast f:weatherSpec.forecasts) {
+
+ SMAQ2OSSProtos.Forecast.Builder fproto = SMAQ2OSSProtos.Forecast.newBuilder();
+
+ fproto.setCondition(f.conditionCode);
+ fproto.setTemperatureMin(f.minTemp-273);
+ fproto.setTemperatureMax(f.maxTemp-273);
+
+ setWeather.addForecasts(fproto);
+ }
+
+ builder.write(normalWriteCharacteristic,createMessage(SMAQ2OSSConstants.MSG_SET_WEATHER,setWeather.build().toByteArray()));
+ builder.queue(getQueue());
+ } catch (Exception ex) {
+ LOG.error("Error sending current weather", ex);
+ }
+ }
+
+ private void setInitialized(TransactionBuilder builder) {
+ builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+ }
+
+ byte[] createMessage(byte msgid, byte[] data){
+
+ ByteBuffer buf=ByteBuffer.allocate(data.length+1);
+ buf.put(msgid);
+ buf.put(data);
+
+ return buf.array();
+ }
+
+ String truncateUTF8(String str, int len){
+
+ if (str ==null)
+ return new String();
+
+ int currLen = str.getBytes(UTF_8).length;
+
+ while (currLen>len-1){
+
+ str=str.substring(0,str.length()-1);
+ currLen = str.getBytes(UTF_8).length;
+ }
+
+ return str;
+ }
+
+ private int getTimestamp(){
+
+ Calendar c = GregorianCalendar.getInstance();
+
+ long offset = (c.get(Calendar.ZONE_OFFSET) + c.get(Calendar.DST_OFFSET));
+ long ts = (c.getTimeInMillis()+ offset)/1000 ;
+
+ return (int)ts;
+
+ }
+
+ SMAQ2OSSSupport setTime(TransactionBuilder builder) {
+
+ SMAQ2OSSProtos.SetTime.Builder settime = SMAQ2OSSProtos.SetTime.newBuilder();
+ settime.setTimestamp(getTimestamp());
+ builder.write(normalWriteCharacteristic,createMessage(SMAQ2OSSConstants.MSG_SET_TIME,settime.build().toByteArray()));
+ return this;
+ }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java
index c94006453..df8fdcf3d 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java
@@ -90,6 +90,7 @@ public enum DeviceType {
PINETIME_JF(190, R.drawable.ic_device_pinetime, R.drawable.ic_device_pinetime_disabled, R.string.devicetype_pinetime_jf),
MIJIA_LYWSD02(200, R.drawable.ic_device_pebble, R.drawable.ic_device_pebble_disabled, R.string.devicetype_mijia_lywsd02),
LEFUN(210, R.drawable.ic_device_h30_h10, R.drawable.ic_device_h30_h10_disabled, R.string.devicetype_lefun),
+ SMAQ2OSS(220, R.drawable.ic_device_default, R.drawable.ic_device_default, R.string.devicetype_smaq2oss),
ITAG(250, R.drawable.ic_device_itag, R.drawable.ic_device_itag_disabled, R.string.devicetype_itag),
NUTMINI(251, R.drawable.ic_device_itag, R.drawable.ic_device_itag_disabled, R.string.devicetype_nut_mini),
VIBRATISSIMO(300, R.drawable.ic_device_lovetoy, R.drawable.ic_device_lovetoy_disabled, R.string.devicetype_vibratissimo),
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java
index 11735a553..77755c9b3 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java
@@ -84,6 +84,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleSupport
import nodomain.freeyourgadget.gadgetbridge.service.devices.pinetime.PineTimeJFSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.roidmi.RoidmiSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.smaq2oss.SMAQ2OSSSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.SonySWR12DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.tlw64.TLW64Support;
import nodomain.freeyourgadget.gadgetbridge.service.devices.um25.Support.UM25Support;
@@ -344,6 +345,8 @@ public class DeviceSupportFactory {
case WASPOS:
deviceSupport = new ServiceDeviceSupport(new WaspOSDeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
break;
+ case SMAQ2OSS:
+ deviceSupport = new ServiceDeviceSupport(new SMAQ2OSSSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
case UM25:
deviceSupport = new ServiceDeviceSupport(new UM25Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
case DOMYOS_T540:
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java
index c29c49ff8..0d41add22 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java
@@ -102,6 +102,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.pinetime.PineTimeJFCoordinat
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.QHybridCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.Roidmi1Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.Roidmi3Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.smaq2oss.SMAQ2OSSCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.sonyswr12.SonySWR12DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.tlw64.TLW64Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.um25.Coordinator.UM25Coordinator;
@@ -300,6 +301,7 @@ public class DeviceHelper {
result.add(new LefunDeviceCoordinator());
result.add(new SonySWR12DeviceCoordinator());
result.add(new WaspOSCoordinator());
+ result.add(new SMAQ2OSSCoordinator());
result.add(new UM25Coordinator());
result.add(new DomyosT540Cooridnator());
diff --git a/app/src/main/proto/smaq2oss.proto b/app/src/main/proto/smaq2oss.proto
new file mode 100644
index 000000000..2e0de5205
--- /dev/null
+++ b/app/src/main/proto/smaq2oss.proto
@@ -0,0 +1,60 @@
+syntax = "proto3";
+
+option java_package = "nodomain.freeyourgadget.gadgetbridge";
+option java_outer_classname = "SMAQ2OSSProtos";
+
+message SetTime
+{
+ int32 timestamp = 1;
+}
+
+message MusicControl
+{
+ int32 music_event = 1;
+}
+
+message CallControl
+{
+ int32 call_event = 1;
+}
+
+message Forecast
+{
+ int32 condition = 1;
+ int32 temperature_min = 2;
+ int32 temperature_max = 3;
+}
+
+message SetWeather
+{
+ int32 timestamp = 1;
+ int32 condition = 2;
+ int32 temperature = 3;
+ int32 temperature_min = 4;
+ int32 temperature_max = 5;
+ int32 humidity = 6;
+ repeated Forecast forecasts = 7;
+}
+
+message MessageNotification
+{
+ uint32 timestamp = 1;
+ int32 type = 2;
+ string sender = 3;
+ string subject = 4;
+ string body = 5;
+}
+
+message CallNotification
+{
+ string number = 1;
+ string name = 2;
+ int32 command = 3;
+}
+
+message MusicInfo
+{
+ string artist = 1;
+ string album = 2;
+ string track = 3;
+}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index cd22b6a73..2773670e5 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -870,6 +870,7 @@
PineTime (JF Firmware)
Sony SWR12
Wasp-os
+ SMA-Q2 OSS
Choose export location
General
diff --git a/build.gradle b/build.gradle
index 95085a2c4..cbeb757e1 100644
--- a/build.gradle
+++ b/build.gradle
@@ -12,6 +12,7 @@ buildscript {
classpath 'com.android.tools.build:gradle:4.2.1'
classpath 'gradle.plugin.com.github.spotbugs:spotbugs-gradle-plugin:2.0.0'
+ classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.8'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}