mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-26 03:46:49 +01:00
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.
This commit is contained in:
parent
1981998fc7
commit
9e6c121dd9
@ -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 <http://www.gnu.org/licenses/>. */
|
||||||
|
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");
|
||||||
|
|
||||||
|
}
|
@ -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 <http://www.gnu.org/licenses/>. */
|
||||||
|
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<? extends ScanFilter> 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<? extends Activity> getAppsManagementActivity() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends Activity> getPairingActivity() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
|
||||||
|
return null;//new WaspOSSampleProvider(device, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -91,6 +91,7 @@ public enum DeviceType {
|
|||||||
VIBRATISSIMO(300, R.drawable.ic_device_lovetoy, R.drawable.ic_device_lovetoy_disabled, R.string.devicetype_vibratissimo),
|
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),
|
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),
|
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);
|
TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_test);
|
||||||
|
|
||||||
private final int key;
|
private final int key;
|
||||||
|
@ -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.sonyswr12.SonySWR12DeviceSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.tlw64.TLW64Support;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.tlw64.TLW64Support;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.vibratissimo.VibratissimoSupport;
|
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.watch9.Watch9DeviceSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xwatch.XWatchSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.xwatch.XWatchSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppe.ZeppESupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppe.ZeppESupport;
|
||||||
@ -320,6 +321,8 @@ public class DeviceSupportFactory {
|
|||||||
break;
|
break;
|
||||||
case SONY_SWR12:
|
case SONY_SWR12:
|
||||||
deviceSupport = new ServiceDeviceSupport(new SonySWR12DeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
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;
|
break;
|
||||||
}
|
}
|
||||||
if (deviceSupport != null) {
|
if (deviceSupport != null) {
|
||||||
|
@ -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 <http://www.gnu.org/licenses/>. */
|
||||||
|
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;i<bytes.length;i+=8) {
|
||||||
|
int l = bytes.length-i;
|
||||||
|
if (l>8) 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<? extends Alarm> 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -99,6 +99,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.Roidmi3Coordinator;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.devices.sonyswr12.SonySWR12DeviceCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.sonyswr12.SonySWR12DeviceCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.tlw64.TLW64Coordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.tlw64.TLW64Coordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.vibratissimo.VibratissimoCoordinator;
|
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.watch9.Watch9DeviceCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.xwatch.XWatchCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.xwatch.XWatchCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimeCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimeCoordinator;
|
||||||
@ -286,6 +287,7 @@ public class DeviceHelper {
|
|||||||
result.add(new SG2Coordinator());
|
result.add(new SG2Coordinator());
|
||||||
result.add(new LefunDeviceCoordinator());
|
result.add(new LefunDeviceCoordinator());
|
||||||
result.add(new SonySWR12DeviceCoordinator());
|
result.add(new SonySWR12DeviceCoordinator());
|
||||||
|
result.add(new WaspOSCoordinator());
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -837,6 +837,7 @@
|
|||||||
<string name="devicetype_tlw64">TLW64</string>
|
<string name="devicetype_tlw64">TLW64</string>
|
||||||
<string name="devicetype_pinetime_jf">PineTime (JF Firmware)</string>
|
<string name="devicetype_pinetime_jf">PineTime (JF Firmware)</string>
|
||||||
<string name="devicetype_sonyswr12" translatable="false">Sony SWR12</string>
|
<string name="devicetype_sonyswr12" translatable="false">Sony SWR12</string>
|
||||||
|
<string name="devicetype_waspos">Wasp OS</string>
|
||||||
<string name="choose_auto_export_location">Choose export location</string>
|
<string name="choose_auto_export_location">Choose export location</string>
|
||||||
<string name="notification_channel_name">Gadgetbridge notifications</string>
|
<string name="notification_channel_name">Gadgetbridge notifications</string>
|
||||||
<string name="notification_channel_high_priority_name">Gadgetbridge notifications high priority</string>
|
<string name="notification_channel_high_priority_name">Gadgetbridge notifications high priority</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user