1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-25 11:26:47 +01:00

Bangle.js: Tidy up data transmit and add data receive

This commit is contained in:
Gordon Williams 2019-12-04 16:42:28 +00:00
parent 43bce3ed80
commit 3c16b246a7
2 changed files with 161 additions and 55 deletions

View File

@ -1,5 +1,15 @@
https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Developer-Documentation
```Bash
./gradlew assembleDebug
adb install app/build/outputs/apk/debug/app-debug.apk adb install app/build/outputs/apk/debug/app-debug.apk
```
Messages sent to Bangle.js from Phone
--------------------------------------
wrapped in `GB(json)\n`
`t:"notify", id:id, src,title,subject,body,sender,tel` - new notification `t:"notify", id:id, src,title,subject,body,sender,tel` - new notification
`t:"notify-", id:id` - delete notification `t:"notify-", id:id` - delete notification
@ -7,6 +17,18 @@ adb install app/build/outputs/apk/debug/app-debug.apk
`t:"find", n:bool` - findDevice `t:"find", n:bool` - findDevice
`t:"vibrate", n:int` - vibrate `t:"vibrate", n:int` - vibrate
`t:"weather", temp,hum,txt,wind,loc` - weather report `t:"weather", temp,hum,txt,wind,loc` - weather report
`t:"musicstate", state,position,shuffle,repeat`
`t:"musicinfo", artist,album,track,dur,c(track count),n(track num)`
"musicstate", state,position,shuffle,repeat Messages from Bangle.js to Phone
"musicinfo", artist,album,track,dur,c(track count),n(track num) --------------------------------
Just raw newline-terminated JSON lines:
`t:"info", msg:"..."`
`t:"warn", msg:"..."`
`t:"error", msg:"..."`
`t:"status", bat:0..100, volt:float(voltage)` - status update
`t:"findPhone", n:bool`
`t:"music", n:"play/pause/next/previous/volumeup/volumedown"`
`t:"call", n:"ACCEPT/END/INCOMING/OUTGOING/REJECT/START/IGNORE"`

View File

@ -1,9 +1,12 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.banglejs; package nodomain.freeyourgadget.gadgetbridge.service.devices.banglejs;
import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.text.DateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List; import java.util.List;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.UUID; import java.util.UUID;
@ -11,21 +14,33 @@ import java.util.UUID;
import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattCharacteristic;
import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.json.JSONArray; import org.json.JSONArray;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSConstants; import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSConstants;
import nodomain.freeyourgadget.gadgetbridge.devices.no1f1.No1F1Constants; import nodomain.freeyourgadget.gadgetbridge.devices.no1f1.No1F1Constants;
import nodomain.freeyourgadget.gadgetbridge.devices.no1f1.No1F1SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.No1F1ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
@ -40,7 +55,6 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport { public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
private static final Logger LOG = LoggerFactory.getLogger(BangleJSDeviceSupport.class); private static final Logger LOG = LoggerFactory.getLogger(BangleJSDeviceSupport.class);
private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
public BluetoothGattCharacteristic rxCharacteristic = null; public BluetoothGattCharacteristic rxCharacteristic = null;
public BluetoothGattCharacteristic txCharacteristic = null; public BluetoothGattCharacteristic txCharacteristic = null;
@ -79,6 +93,7 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
/// Write a string of data, and chunk it up /// Write a string of data, and chunk it up
public void uartTx(TransactionBuilder builder, String str) { public void uartTx(TransactionBuilder builder, String str) {
LOG.info("UART TX: ", str);
byte bytes[]; byte bytes[];
try { try {
bytes = str.getBytes("UTF-8"); bytes = str.getBytes("UTF-8");
@ -96,6 +111,97 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
} }
} }
/// Write a string of data, and chunk it up
public 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);
}
}
void handleUartRxLine(String line) {
LOG.info("UART RX LINE: " + line);
if (line==">Uncaught ReferenceError: \"gb\" is not defined")
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);
}
}
}
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 "activity": {
BangleJSActivitySample sample = new BangleJSActivitySample();
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();
BangleJSSampleProvider provider = new BangleJSSampleProvider(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 @Override
public boolean onCharacteristicChanged(BluetoothGatt gatt, public boolean onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) { BluetoothGattCharacteristic characteristic) {
@ -111,8 +217,7 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
int p = receivedLine.indexOf("\n"); int p = receivedLine.indexOf("\n");
String line = receivedLine.substring(0,p-1); String line = receivedLine.substring(0,p-1);
receivedLine = receivedLine.substring(p+1); receivedLine = receivedLine.substring(p+1);
LOG.info("RX LINE: " + line); handleUartRxLine(line);
// TODO: parse this into JSON and handle it
} }
} }
return false; return false;
@ -120,19 +225,17 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
void setTime(TransactionBuilder builder) { void setTime(TransactionBuilder builder) {
uartTx(builder, "\u0010setTime("+(System.currentTimeMillis()/1000)+");E.setTimeZone("+(TimeZone.getDefault().getRawOffset()/3600000)+");\n"); uartTx(builder, "\u0010setTime("+(System.currentTimeMillis()/1000)+");E.setTimeZone("+(TimeZone.getDefault().getRawOffset()/3600000)+");\n");
} }
@Override @Override
public boolean useAutoConnect() { public boolean useAutoConnect() {
return false; return true;
} }
@Override @Override
public void onNotification(NotificationSpec notificationSpec) { public void onNotification(NotificationSpec notificationSpec) {
try { try {
TransactionBuilder builder = performInitialized("onNotification");
JSONObject o = new JSONObject(); JSONObject o = new JSONObject();
o.put("t", "notify"); o.put("t", "notify");
o.put("id", notificationSpec.getId()); o.put("id", notificationSpec.getId());
@ -142,25 +245,21 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
o.put("body", notificationSpec.body); o.put("body", notificationSpec.body);
o.put("sender", notificationSpec.sender); o.put("sender", notificationSpec.sender);
o.put("tel", notificationSpec.phoneNumber); o.put("tel", notificationSpec.phoneNumber);
uartTxJSON("onNotification", o);
uartTx(builder, "\u0010gb("+o.toString()+")\n"); } catch (JSONException e) {
builder.queue(getQueue()); LOG.info("JSONException: " + e.getLocalizedMessage());
} catch (Exception e) {
GB.toast(getContext(), "Error setting notification: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
} }
} }
@Override @Override
public void onDeleteNotification(int id) { public void onDeleteNotification(int id) {
try { try {
TransactionBuilder builder = performInitialized("onDeleteNotification");
JSONObject o = new JSONObject(); JSONObject o = new JSONObject();
o.put("t", "notify-"); o.put("t", "notify-");
o.put("id", id); o.put("id", id);
uartTx(builder, "\u0010gb("+o.toString()+")\n"); uartTxJSON("onDeleteNotification", o);
builder.queue(getQueue()); } catch (JSONException e) {
} catch (Exception e) { LOG.info("JSONException: " + e.getLocalizedMessage());
GB.toast(getContext(), "Error deleting notification: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
} }
} }
@ -178,7 +277,6 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
@Override @Override
public void onSetAlarms(ArrayList<? extends Alarm> alarms) { public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
try { try {
TransactionBuilder builder = performInitialized("onSetAlarms");
JSONObject o = new JSONObject(); JSONObject o = new JSONObject();
o.put("t", "alarm"); o.put("t", "alarm");
JSONArray jsonalarms = new JSONArray(); JSONArray jsonalarms = new JSONArray();
@ -194,30 +292,26 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
jsonalarm.put("h", alarm.getHour()); jsonalarm.put("h", alarm.getHour());
jsonalarm.put("m", alarm.getMinute()); jsonalarm.put("m", alarm.getMinute());
} }
uartTxJSON("onSetAlarms", o);
uartTx(builder, "\u0010gb("+o.toString()+")\n"); } catch (JSONException e) {
builder.queue(getQueue()); LOG.info("JSONException: " + e.getLocalizedMessage());
} catch (Exception e) {
GB.toast(getContext(), "Error setting alarms: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
} }
} }
@Override @Override
public void onSetCallState(CallSpec callSpec) { public void onSetCallState(CallSpec callSpec) {
try { try {
TransactionBuilder builder = performInitialized("onSetCallState");
JSONObject o = new JSONObject(); JSONObject o = new JSONObject();
o.put("t", "call"); o.put("t", "call");
String cmdString[] = {"","undefined","accept","incoming","outgoing","reject","start","end"}; String cmdString[] = {"","undefined","accept","incoming","outgoing","reject","start","end"};
o.put("cmd", cmdString[callSpec.command]); o.put("cmd", cmdString[callSpec.command]);
o.put("name", callSpec.name); o.put("name", callSpec.name);
o.put("number", callSpec.number); o.put("number", callSpec.number);
uartTx(builder, "\u0010gb("+o.toString()+")\n"); uartTxJSON("onSetCallState", o);
builder.queue(getQueue()); } catch (JSONException e) {
} catch (Exception e) { LOG.info("JSONException: " + e.getLocalizedMessage());
GB.toast(getContext(), "Error setting call state: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
} }
} }
@Override @Override
public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) { public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
@ -227,7 +321,6 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
@Override @Override
public void onSetMusicState(MusicStateSpec stateSpec) { public void onSetMusicState(MusicStateSpec stateSpec) {
try { try {
TransactionBuilder builder = performInitialized("onSetMusicState");
JSONObject o = new JSONObject(); JSONObject o = new JSONObject();
o.put("t", "musicstate"); o.put("t", "musicstate");
String musicStates[] = {"play","pause","stop",""}; String musicStates[] = {"play","pause","stop",""};
@ -235,17 +328,15 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
o.put("position", stateSpec.position); o.put("position", stateSpec.position);
o.put("shuffle", stateSpec.shuffle); o.put("shuffle", stateSpec.shuffle);
o.put("repeat", stateSpec.repeat); o.put("repeat", stateSpec.repeat);
uartTx(builder, "\u0010gb("+o.toString()+")\n"); uartTxJSON("onSetMusicState", o);
builder.queue(getQueue()); } catch (JSONException e) {
} catch (Exception e) { LOG.info("JSONException: " + e.getLocalizedMessage());
GB.toast(getContext(), "Error setting Music state: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
} }
} }
@Override @Override
public void onSetMusicInfo(MusicSpec musicSpec) { public void onSetMusicInfo(MusicSpec musicSpec) {
try { try {
TransactionBuilder builder = performInitialized("onSetMusicInfo");
JSONObject o = new JSONObject(); JSONObject o = new JSONObject();
o.put("t", "musicinfo"); o.put("t", "musicinfo");
o.put("artist", musicSpec.artist); o.put("artist", musicSpec.artist);
@ -254,10 +345,9 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
o.put("dur", musicSpec.duration); o.put("dur", musicSpec.duration);
o.put("c", musicSpec.trackCount); o.put("c", musicSpec.trackCount);
o.put("n", musicSpec.trackNr); o.put("n", musicSpec.trackNr);
uartTx(builder, "\u0010gb("+o.toString()+")\n"); uartTxJSON("onSetMusicInfo", o);
builder.queue(getQueue()); } catch (JSONException e) {
} catch (Exception e) { LOG.info("JSONException: " + e.getLocalizedMessage());
GB.toast(getContext(), "Error setting Music info: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
} }
} }
@ -319,28 +409,24 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
@Override @Override
public void onFindDevice(boolean start) { public void onFindDevice(boolean start) {
try { try {
TransactionBuilder builder = performInitialized("onFindDevice");
JSONObject o = new JSONObject(); JSONObject o = new JSONObject();
o.put("t", "find"); o.put("t", "find");
o.put("n", start); o.put("n", start);
uartTx(builder, "\u0010gb("+o.toString()+")\n"); uartTxJSON("onFindDevice", o);
builder.queue(getQueue()); } catch (JSONException e) {
} catch (Exception e) { LOG.info("JSONException: " + e.getLocalizedMessage());
GB.toast(getContext(), "Error finding device: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
} }
} }
@Override @Override
public void onSetConstantVibration(int integer) { public void onSetConstantVibration(int integer) {
try { try {
TransactionBuilder builder = performInitialized("onSetConstantVibration");
JSONObject o = new JSONObject(); JSONObject o = new JSONObject();
o.put("t", "vibrate"); o.put("t", "vibrate");
o.put("n", integer); o.put("n", integer);
uartTx(builder, "\u0010gb("+o.toString()+")\n"); uartTxJSON("onSetConstantVibration", o);
builder.queue(getQueue()); } catch (JSONException e) {
} catch (Exception e) { LOG.info("JSONException: " + e.getLocalizedMessage());
GB.toast(getContext(), "Error vibrating: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
} }
} }
@ -387,7 +473,6 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
@Override @Override
public void onSendWeather(WeatherSpec weatherSpec) { public void onSendWeather(WeatherSpec weatherSpec) {
try { try {
TransactionBuilder builder = performInitialized("onSendWeather");
JSONObject o = new JSONObject(); JSONObject o = new JSONObject();
o.put("t", "weather"); o.put("t", "weather");
o.put("temp", weatherSpec.currentTemp); o.put("temp", weatherSpec.currentTemp);
@ -395,10 +480,9 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
o.put("txt", weatherSpec.currentCondition); o.put("txt", weatherSpec.currentCondition);
o.put("wind", weatherSpec.windSpeed); o.put("wind", weatherSpec.windSpeed);
o.put("loc", weatherSpec.location); o.put("loc", weatherSpec.location);
uartTx(builder, "\u0010gb("+o.toString()+")\n"); uartTxJSON("onSendWeather", o);
builder.queue(getQueue()); } catch (JSONException e) {
} catch (Exception e) { LOG.info("JSONException: " + e.getLocalizedMessage());
GB.toast(getContext(), "Error showing weather: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
} }
} }
} }