From 9e6c121dd9b4761da40109732a620164fb88860b Mon Sep 17 00:00:00 2001 From: Daniel Thompson Date: Wed, 23 Dec 2020 20:55:23 +0000 Subject: [PATCH] wasp-os: Introduce a new device type wasp-os is an open-source firmware, based on MicroPython, for smart watches that are based on the nRF52 family of microcontrollers. Currently this includes the hacker friendly PineTime from Pine64 as well as the Colmi P8, which is a popular device with watch modders. For GadgetBridge integration wasp-os is very similar to handling Espruino/BangleJS: the watch will interact with the bridge by providing REPL-over-BLE via the Nordic UART service. wasp-os implements the same JSON-wrapped-in-GB()-function-call protocol used for BangleJS buy the differences in capability between the two firmwares lead us to introducing a different device type... and also a different icon since the lead devices for wasp-os are square! I plan to keep as aligned as possible with the BangleJS device type, however at present the major differences include: 1. Specify the bonding style (wasp-os currently does not support bonding... so no need to ask). 2. Disable some of the not-yet-implemented features. 3. Reduce the minimum packet size to fix communication reliability issues with the MicroPython NUS implementation. 4. Switch the date/time setting syntax from JavaScript to MicroPython. --- .../devices/waspos/WaspOSConstants.java | 28 + .../devices/waspos/WaspOSCoordinator.java | 167 ++++++ .../gadgetbridge/model/DeviceType.java | 1 + .../service/DeviceSupportFactory.java | 3 + .../devices/waspos/WaspOSDeviceSupport.java | 507 ++++++++++++++++++ .../gadgetbridge/util/DeviceHelper.java | 2 + app/src/main/res/values/strings.xml | 1 + 7 files changed, 709 insertions(+) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/waspos/WaspOSConstants.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/waspos/WaspOSCoordinator.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/waspos/WaspOSDeviceSupport.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/waspos/WaspOSConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/waspos/WaspOSConstants.java new file mode 100644 index 000000000..df5c29378 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/waspos/WaspOSConstants.java @@ -0,0 +1,28 @@ +/* Copyright (C) 2019-2020 Gordon Williams + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.waspos; + +import java.util.UUID; + +public final class WaspOSConstants { + + + public static final UUID UUID_SERVICE_NORDIC_UART = UUID.fromString("6e400001-b5a3-f393-e0a9-e50e24dcca9e"); + public static final UUID UUID_CHARACTERISTIC_NORDIC_UART_TX = UUID.fromString("6e400002-b5a3-f393-e0a9-e50e24dcca9e"); + public static final UUID UUID_CHARACTERISTIC_NORDIC_UART_RX = UUID.fromString("6e400003-b5a3-f393-e0a9-e50e24dcca9e"); + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/waspos/WaspOSCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/waspos/WaspOSCoordinator.java new file mode 100644 index 000000000..772868713 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/waspos/WaspOSCoordinator.java @@ -0,0 +1,167 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti, Gordon Williams, José Rebelo + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.waspos; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.bluetooth.le.ScanFilter; +import android.content.Context; +import android.net.Uri; +import android.os.Build; +import android.os.ParcelUuid; + +import androidx.annotation.NonNull; + +import java.util.Collection; +import java.util.Collections; + +import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; +import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; +import nodomain.freeyourgadget.gadgetbridge.devices.waspos.WaspOSConstants; +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 WaspOSCoordinator extends AbstractDeviceCoordinator { + + @Override + public DeviceType getDeviceType() { + return DeviceType.WASPOS; + } + + @Override + public String getManufacturer() { + return "MicroPython"; + } + + @NonNull + @Override + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public Collection createBLEScanFilters() { + // TODO: filter on name beginning Bangle.js? Doesn't appear to be built-in :( + // https://developer.android.com/reference/android/bluetooth/le/ScanFilter.Builder.html#setDeviceName(java.lang.String) + ParcelUuid hpService = new ParcelUuid(WaspOSConstants.UUID_SERVICE_NORDIC_UART); + ScanFilter filter = new ScanFilter.Builder().setServiceUuid(hpService).build(); + return Collections.singletonList(filter); + } + + @NonNull + @Override + public DeviceType getSupportedType(GBDeviceCandidate candidate) { + String name = candidate.getDevice().getName(); + /* Filter by Espruino devices to avoid getting + the device chooser full of spam devices. */ + if (name != null && ( + name.startsWith("DS-D6") || + name.startsWith("K9") || + name.startsWith("PineTime") || + name.startsWith("P8"))) + return DeviceType.WASPOS; + + return DeviceType.UNKNOWN; + } + + @Override + public int getBondingStyle(){ + return BONDING_STYLE_NONE; + } + + @Override + public boolean supportsCalendarEvents() { + return false; + } + + @Override + public boolean supportsRealtimeData() { + return false; + } + + @Override + public boolean supportsWeather() { + return false; + } + + @Override + public boolean supportsFindDevice() { + return true; + } + + @Override + public boolean supportsActivityDataFetching() { + return false; + } + + @Override + public boolean supportsActivityTracking() { + return false; + } + + @Override + public boolean supportsScreenshots() { + return false; + } + + @Override + public boolean supportsSmartWakeup(GBDevice device) { + return false; + } + + @Override + public boolean supportsHeartRateMeasurement(GBDevice device) { + return false; + } + + @Override + public boolean supportsAppsManagement() { + return false; + } + + @Override + public int getAlarmSlotCount() { + return 0; + } + + @Override + public Class getAppsManagementActivity() { + return null; + } + + + @Override + protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) { + } + + @Override + public Class getPairingActivity() { + return null; + } + + @Override + public SampleProvider getSampleProvider(GBDevice device, DaoSession session) { + return null;//new WaspOSSampleProvider(device, session); + } + + @Override + public InstallHandler findInstallHandler(Uri uri, Context context) { + return null; + } +} 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 a71d3980e..7fdb68f14 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java @@ -91,6 +91,7 @@ public enum DeviceType { VIBRATISSIMO(300, R.drawable.ic_device_lovetoy, R.drawable.ic_device_lovetoy_disabled, R.string.devicetype_vibratissimo), SONY_SWR12(310, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_sonyswr12), LIVEVIEW(320, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_liveview), + WASPOS(330, R.drawable.ic_device_pebble, R.drawable.ic_device_pebble_disabled, R.string.devicetype_waspos), TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_test); private final int key; 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 bdf0a947a..95cb6c3f5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java @@ -81,6 +81,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.roidmi.RoidmiSupport import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.SonySWR12DeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.tlw64.TLW64Support; import nodomain.freeyourgadget.gadgetbridge.service.devices.vibratissimo.VibratissimoSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.waspos.WaspOSDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.watch9.Watch9DeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.xwatch.XWatchSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppe.ZeppESupport; @@ -320,6 +321,8 @@ public class DeviceSupportFactory { break; case SONY_SWR12: deviceSupport = new ServiceDeviceSupport(new SonySWR12DeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); + case WASPOS: + deviceSupport = new ServiceDeviceSupport(new WaspOSDeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); break; } if (deviceSupport != null) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/waspos/WaspOSDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/waspos/WaspOSDeviceSupport.java new file mode 100644 index 000000000..39fbde24f --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/waspos/WaspOSDeviceSupport.java @@ -0,0 +1,507 @@ +/* Copyright (C) 2019-2020 Andreas Shimokawa, Gordon Williams + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.waspos; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; +import android.content.Context; +import android.net.Uri; +import android.widget.Toast; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.SimpleTimeZone; +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl; +import nodomain.freeyourgadget.gadgetbridge.devices.waspos.WaspOSConstants; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.Alarm; +import nodomain.freeyourgadget.gadgetbridge.model.BatteryState; +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.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils; +import nodomain.freeyourgadget.gadgetbridge.util.GB; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; + +public class WaspOSDeviceSupport extends AbstractBTLEDeviceSupport { + private static final Logger LOG = LoggerFactory.getLogger(WaspOSDeviceSupport.class); + private BluetoothGattCharacteristic rxCharacteristic = null; + private BluetoothGattCharacteristic txCharacteristic = null; + + private String receivedLine = ""; + + public WaspOSDeviceSupport() { + super(LOG); + addSupportedService(WaspOSConstants.UUID_SERVICE_NORDIC_UART); + } + + @Override + protected TransactionBuilder initializeDevice(TransactionBuilder builder) { + LOG.info("Initializing"); + + gbDevice.setState(GBDevice.State.INITIALIZING); + gbDevice.sendDeviceUpdateIntent(getContext()); + + rxCharacteristic = getCharacteristic(WaspOSConstants.UUID_CHARACTERISTIC_NORDIC_UART_RX); + txCharacteristic = getCharacteristic(WaspOSConstants.UUID_CHARACTERISTIC_NORDIC_UART_TX); + builder.setGattCallback(this); + builder.notify(rxCharacteristic, true); + + uartTx(builder, " \u0003"); // clear active line + + Prefs prefs = GBApplication.getPrefs(); + if (prefs.getBoolean("datetime_synconconnect", true)) + setTime(builder); + //sendSettings(builder); + + // get version + + gbDevice.setState(GBDevice.State.INITIALIZED); + gbDevice.sendDeviceUpdateIntent(getContext()); + + LOG.info("Initialization Done"); + + return builder; + } + + /// Write a string of data, and chunk it up + private void uartTx(TransactionBuilder builder, String str) { + LOG.info("UART TX: " + str); + byte[] bytes; + bytes = str.getBytes(StandardCharsets.ISO_8859_1); + for (int i=0;i8) l=8; + byte[] packet = new byte[l]; + System.arraycopy(bytes, i, packet, 0, l); + builder.write(txCharacteristic, packet); + } + } + + /// Write a string of data, and chunk it up + private void uartTxJSON(String taskName, JSONObject json) { + try { + TransactionBuilder builder = performInitialized(taskName); + uartTx(builder, "\u0010GB("+json.toString()+")\n"); + builder.queue(getQueue()); + } catch (IOException e) { + GB.toast(getContext(), "Error in "+taskName+": " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR); + } + } + + private void handleUartRxLine(String line) { + LOG.info("UART RX LINE: " + line); + + if (">Uncaught ReferenceError: \"gb\" is not defined".equals(line)) + GB.toast(getContext(), "Gadgetbridge plugin not installed on Bangle.js", Toast.LENGTH_LONG, GB.ERROR); + else if (line.charAt(0)=='{') { + // JSON - we hope! + try { + JSONObject json = new JSONObject(line); + handleUartRxJSON(json); + } catch (JSONException e) { + GB.toast(getContext(), "Malformed JSON from Bangle.js: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR); + } + } + } + + private void handleUartRxJSON(JSONObject json) throws JSONException { + switch (json.getString("t")) { + case "info": + GB.toast(getContext(), "Bangle.js: " + json.getString("msg"), Toast.LENGTH_LONG, GB.INFO); + break; + case "warn": + GB.toast(getContext(), "Bangle.js: " + json.getString("msg"), Toast.LENGTH_LONG, GB.WARN); + break; + case "error": + GB.toast(getContext(), "Bangle.js: " + json.getString("msg"), Toast.LENGTH_LONG, GB.ERROR); + break; + case "status": { + Context context = getContext(); + if (json.has("bat")) { + int b = json.getInt("bat"); + if (b<0) b=0; + if (b>100) b=100; + gbDevice.setBatteryLevel((short)b); + if (b < 30) { + gbDevice.setBatteryState(BatteryState.BATTERY_LOW); + GB.updateBatteryNotification(context.getString(R.string.notif_battery_low_percent, gbDevice.getName(), String.valueOf(b)), "", context); + } else { + gbDevice.setBatteryState(BatteryState.BATTERY_NORMAL); + GB.removeBatteryNotification(context); + } + } + if (json.has("volt")) + gbDevice.setBatteryVoltage((float)json.getDouble("volt")); + gbDevice.sendDeviceUpdateIntent(context); + } break; + case "findPhone": { + boolean start = json.has("n") && json.getBoolean("n"); + GBDeviceEventFindPhone deviceEventFindPhone = new GBDeviceEventFindPhone(); + deviceEventFindPhone.event = start ? GBDeviceEventFindPhone.Event.START : GBDeviceEventFindPhone.Event.STOP; + evaluateGBDeviceEvent(deviceEventFindPhone); + } break; + case "music": { + GBDeviceEventMusicControl deviceEventMusicControl = new GBDeviceEventMusicControl(); + deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.valueOf(json.getString("n").toUpperCase()); + evaluateGBDeviceEvent(deviceEventMusicControl); + } break; + case "call": { + GBDeviceEventCallControl deviceEventCallControl = new GBDeviceEventCallControl(); + deviceEventCallControl.event = GBDeviceEventCallControl.Event.valueOf(json.getString("n").toUpperCase()); + evaluateGBDeviceEvent(deviceEventCallControl); + } break; + case "notify" : { + GBDeviceEventNotificationControl deviceEvtNotificationControl = new GBDeviceEventNotificationControl(); + // .title appears unused + deviceEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.valueOf(json.getString("n").toUpperCase()); + if (json.has("id")) + deviceEvtNotificationControl.handle = json.getInt("id"); + if (json.has("tel")) + deviceEvtNotificationControl.phoneNumber = json.getString("tel"); + if (json.has("msg")) + deviceEvtNotificationControl.reply = json.getString("msg"); + evaluateGBDeviceEvent(deviceEvtNotificationControl); + } break; + /*case "activity": { + WaspOSActivitySample sample = new WaspOSActivitySample(); + sample.setTimestamp((int) (GregorianCalendar.getInstance().getTimeInMillis() / 1000L)); + sample.setHeartRate(json.getInteger("hrm")); + try (DBHandler dbHandler = GBApplication.acquireDB()) { + Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId(); + Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId(); + WaspOSSampleProvider provider = new WaspOSSampleProvider(getDevice(), dbHandler.getDaoSession()); + sample.setDeviceId(deviceId); + sample.setUserId(userId); + provider.addGBActivitySample(sample); + } catch (Exception ex) { + LOG.warn("Error saving current heart rate: " + ex.getLocalizedMessage()); + } + } break;*/ + } + } + + @Override + public boolean onCharacteristicChanged(BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic) { + if (super.onCharacteristicChanged(gatt, characteristic)) { + return true; + } + if (WaspOSConstants.UUID_CHARACTERISTIC_NORDIC_UART_RX.equals(characteristic.getUuid())) { + byte[] chars = characteristic.getValue(); + String packetStr = new String(chars); + LOG.info("RX: " + packetStr); + receivedLine += packetStr; + while (receivedLine.contains("\n")) { + int p = receivedLine.indexOf("\n"); + String line = receivedLine.substring(0,p-1); + receivedLine = receivedLine.substring(p+1); + handleUartRxLine(line); + } + } + return false; + } + + + void setTime(TransactionBuilder builder) { + DateTimeFormatter dtf = DateTimeFormatter.ofPattern("(yyyy, MM, dd, HH, mm, ss)"); + LocalDateTime now = LocalDateTime.now(); + String cmd = "\u0010watch.rtc.set_localtime("+dtf.format(now)+")\n"; + uartTx(builder, cmd+"\n"); + } + + @Override + public boolean useAutoConnect() { + return true; + } + + @Override + public void onNotification(NotificationSpec notificationSpec) { + try { + JSONObject o = new JSONObject(); + o.put("t", "notify"); + o.put("id", notificationSpec.getId()); + o.put("src", notificationSpec.sourceName); + o.put("title", notificationSpec.title); + o.put("subject", notificationSpec.subject); + o.put("body", notificationSpec.body); + o.put("sender", notificationSpec.sender); + o.put("tel", notificationSpec.phoneNumber); + uartTxJSON("onNotification", o); + } catch (JSONException e) { + LOG.info("JSONException: " + e.getLocalizedMessage()); + } + } + + @Override + public void onDeleteNotification(int id) { + try { + JSONObject o = new JSONObject(); + o.put("t", "notify-"); + o.put("id", id); + uartTxJSON("onDeleteNotification", o); + } catch (JSONException e) { + LOG.info("JSONException: " + e.getLocalizedMessage()); + } + } + + @Override + public void onSetTime() { + try { + TransactionBuilder builder = performInitialized("setTime"); + setTime(builder); + builder.queue(getQueue()); + } catch (Exception e) { + GB.toast(getContext(), "Error setting time: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR); + } + } + + @Override + public void onSetAlarms(ArrayList alarms) { + try { + JSONObject o = new JSONObject(); + o.put("t", "alarm"); + JSONArray jsonalarms = new JSONArray(); + o.put("d", jsonalarms); + + for (Alarm alarm : alarms) { + if (!alarm.getEnabled()) continue; + JSONObject jsonalarm = new JSONObject(); + jsonalarms.put(jsonalarm); + + Calendar calendar = AlarmUtils.toCalendar(alarm); + // TODO: getRepetition to ensure it only happens on correct day? + jsonalarm.put("h", alarm.getHour()); + jsonalarm.put("m", alarm.getMinute()); + } + uartTxJSON("onSetAlarms", o); + } catch (JSONException e) { + LOG.info("JSONException: " + e.getLocalizedMessage()); + } + } + + @Override + public void onSetCallState(CallSpec callSpec) { + try { + JSONObject o = new JSONObject(); + o.put("t", "call"); + String[] cmdString = {"", "undefined", "accept", "incoming", "outgoing", "reject", "start", "end"}; + o.put("cmd", cmdString[callSpec.command]); + o.put("name", callSpec.name); + o.put("number", callSpec.number); + uartTxJSON("onSetCallState", o); + } catch (JSONException e) { + LOG.info("JSONException: " + e.getLocalizedMessage()); + } +} + + @Override + public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) { + + } + + @Override + public void onSetMusicState(MusicStateSpec stateSpec) { + try { + JSONObject o = new JSONObject(); + o.put("t", "musicstate"); + String[] musicStates = {"play", "pause", "stop", ""}; + o.put("state", musicStates[stateSpec.state]); + o.put("position", stateSpec.position); + o.put("shuffle", stateSpec.shuffle); + o.put("repeat", stateSpec.repeat); + uartTxJSON("onSetMusicState", o); + } catch (JSONException e) { + LOG.info("JSONException: " + e.getLocalizedMessage()); + } + } + + @Override + public void onSetMusicInfo(MusicSpec musicSpec) { + try { + JSONObject o = new JSONObject(); + o.put("t", "musicinfo"); + o.put("artist", musicSpec.artist); + o.put("album", musicSpec.album); + o.put("track", musicSpec.track); + o.put("dur", musicSpec.duration); + o.put("c", musicSpec.trackCount); + o.put("n", musicSpec.trackNr); + uartTxJSON("onSetMusicInfo", o); + } catch (JSONException e) { + LOG.info("JSONException: " + e.getLocalizedMessage()); + } + } + + @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) { + try { + JSONObject o = new JSONObject(); + o.put("t", "find"); + o.put("n", start); + uartTxJSON("onFindDevice", o); + } catch (JSONException e) { + LOG.info("JSONException: " + e.getLocalizedMessage()); + } + } + + @Override + public void onSetConstantVibration(int integer) { + try { + JSONObject o = new JSONObject(); + o.put("t", "vibrate"); + o.put("n", integer); + uartTxJSON("onSetConstantVibration", o); + } catch (JSONException e) { + LOG.info("JSONException: " + e.getLocalizedMessage()); + } + } + + @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 { + JSONObject o = new JSONObject(); + o.put("t", "weather"); + o.put("temp", weatherSpec.currentTemp); + o.put("hum", weatherSpec.currentHumidity); + o.put("txt", weatherSpec.currentCondition); + o.put("wind", weatherSpec.windSpeed); + o.put("loc", weatherSpec.location); + uartTxJSON("onSendWeather", o); + } catch (JSONException e) { + LOG.info("JSONException: " + e.getLocalizedMessage()); + } + } +} 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 1fa421504..1662253bd 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java @@ -99,6 +99,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.Roidmi3Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.sonyswr12.SonySWR12DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.tlw64.TLW64Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.vibratissimo.VibratissimoCoordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.waspos.WaspOSCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.watch9.Watch9DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.xwatch.XWatchCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimeCoordinator; @@ -286,6 +287,7 @@ public class DeviceHelper { result.add(new SG2Coordinator()); result.add(new LefunDeviceCoordinator()); result.add(new SonySWR12DeviceCoordinator()); + result.add(new WaspOSCoordinator()); return result; } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f61bc75a3..2884bdd53 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -837,6 +837,7 @@ TLW64 PineTime (JF Firmware) Sony SWR12 + Wasp OS Choose export location Gadgetbridge notifications Gadgetbridge notifications high priority