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 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 getPairingActivity() { + return null; + } + + @Override + public boolean supportsActivityDataFetching() { + return false; + } + + @Override + public boolean supportsActivityTracking() { + return false; + } + + @Override + public SampleProvider 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 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 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 }