1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-12-26 18:45:49 +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
```
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` - delete notification
@ -7,6 +17,18 @@ adb install app/build/outputs/apk/debug/app-debug.apk
`t:"find", n:bool` - findDevice
`t:"vibrate", n:int` - vibrate
`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
"musicinfo", artist,album,track,dur,c(track count),n(track num)
Messages from Bangle.js to Phone
--------------------------------
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;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.TimeZone;
import java.util.UUID;
@ -11,21 +14,33 @@ import java.util.UUID;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.Context;
import android.net.Uri;
import android.widget.Toast;
import androidx.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.json.JSONException;
import org.json.JSONObject;
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.GBDeviceEventCallControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSConstants;
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.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;
@ -40,7 +55,6 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
private static final Logger LOG = LoggerFactory.getLogger(BangleJSDeviceSupport.class);
private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
public BluetoothGattCharacteristic rxCharacteristic = null;
public BluetoothGattCharacteristic txCharacteristic = null;
@ -79,6 +93,7 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
/// Write a string of data, and chunk it up
public void uartTx(TransactionBuilder builder, String str) {
LOG.info("UART TX: ", str);
byte bytes[];
try {
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
public boolean onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
@ -111,8 +217,7 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
int p = receivedLine.indexOf("\n");
String line = receivedLine.substring(0,p-1);
receivedLine = receivedLine.substring(p+1);
LOG.info("RX LINE: " + line);
// TODO: parse this into JSON and handle it
handleUartRxLine(line);
}
}
return false;
@ -120,19 +225,17 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
void setTime(TransactionBuilder builder) {
uartTx(builder, "\u0010setTime("+(System.currentTimeMillis()/1000)+");E.setTimeZone("+(TimeZone.getDefault().getRawOffset()/3600000)+");\n");
}
@Override
public boolean useAutoConnect() {
return false;
return true;
}
@Override
public void onNotification(NotificationSpec notificationSpec) {
try {
TransactionBuilder builder = performInitialized("onNotification");
JSONObject o = new JSONObject();
o.put("t", "notify");
o.put("id", notificationSpec.getId());
@ -142,25 +245,21 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
o.put("body", notificationSpec.body);
o.put("sender", notificationSpec.sender);
o.put("tel", notificationSpec.phoneNumber);
uartTx(builder, "\u0010gb("+o.toString()+")\n");
builder.queue(getQueue());
} catch (Exception e) {
GB.toast(getContext(), "Error setting notification: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
uartTxJSON("onNotification", o);
} catch (JSONException e) {
LOG.info("JSONException: " + e.getLocalizedMessage());
}
}
@Override
public void onDeleteNotification(int id) {
try {
TransactionBuilder builder = performInitialized("onDeleteNotification");
JSONObject o = new JSONObject();
o.put("t", "notify-");
o.put("id", id);
uartTx(builder, "\u0010gb("+o.toString()+")\n");
builder.queue(getQueue());
} catch (Exception e) {
GB.toast(getContext(), "Error deleting notification: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
uartTxJSON("onDeleteNotification", o);
} catch (JSONException e) {
LOG.info("JSONException: " + e.getLocalizedMessage());
}
}
@ -178,7 +277,6 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
@Override
public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
try {
TransactionBuilder builder = performInitialized("onSetAlarms");
JSONObject o = new JSONObject();
o.put("t", "alarm");
JSONArray jsonalarms = new JSONArray();
@ -194,30 +292,26 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
jsonalarm.put("h", alarm.getHour());
jsonalarm.put("m", alarm.getMinute());
}
uartTx(builder, "\u0010gb("+o.toString()+")\n");
builder.queue(getQueue());
} catch (Exception e) {
GB.toast(getContext(), "Error setting alarms: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
uartTxJSON("onSetAlarms", o);
} catch (JSONException e) {
LOG.info("JSONException: " + e.getLocalizedMessage());
}
}
@Override
public void onSetCallState(CallSpec callSpec) {
try {
TransactionBuilder builder = performInitialized("onSetCallState");
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);
uartTx(builder, "\u0010gb("+o.toString()+")\n");
builder.queue(getQueue());
} catch (Exception e) {
GB.toast(getContext(), "Error setting call state: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
uartTxJSON("onSetCallState", o);
} catch (JSONException e) {
LOG.info("JSONException: " + e.getLocalizedMessage());
}
}
}
@Override
public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
@ -227,7 +321,6 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
@Override
public void onSetMusicState(MusicStateSpec stateSpec) {
try {
TransactionBuilder builder = performInitialized("onSetMusicState");
JSONObject o = new JSONObject();
o.put("t", "musicstate");
String musicStates[] = {"play","pause","stop",""};
@ -235,17 +328,15 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
o.put("position", stateSpec.position);
o.put("shuffle", stateSpec.shuffle);
o.put("repeat", stateSpec.repeat);
uartTx(builder, "\u0010gb("+o.toString()+")\n");
builder.queue(getQueue());
} catch (Exception e) {
GB.toast(getContext(), "Error setting Music state: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
uartTxJSON("onSetMusicState", o);
} catch (JSONException e) {
LOG.info("JSONException: " + e.getLocalizedMessage());
}
}
@Override
public void onSetMusicInfo(MusicSpec musicSpec) {
try {
TransactionBuilder builder = performInitialized("onSetMusicInfo");
JSONObject o = new JSONObject();
o.put("t", "musicinfo");
o.put("artist", musicSpec.artist);
@ -254,10 +345,9 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
o.put("dur", musicSpec.duration);
o.put("c", musicSpec.trackCount);
o.put("n", musicSpec.trackNr);
uartTx(builder, "\u0010gb("+o.toString()+")\n");
builder.queue(getQueue());
} catch (Exception e) {
GB.toast(getContext(), "Error setting Music info: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
uartTxJSON("onSetMusicInfo", o);
} catch (JSONException e) {
LOG.info("JSONException: " + e.getLocalizedMessage());
}
}
@ -319,28 +409,24 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
@Override
public void onFindDevice(boolean start) {
try {
TransactionBuilder builder = performInitialized("onFindDevice");
JSONObject o = new JSONObject();
o.put("t", "find");
o.put("n", start);
uartTx(builder, "\u0010gb("+o.toString()+")\n");
builder.queue(getQueue());
} catch (Exception e) {
GB.toast(getContext(), "Error finding device: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
uartTxJSON("onFindDevice", o);
} catch (JSONException e) {
LOG.info("JSONException: " + e.getLocalizedMessage());
}
}
@Override
public void onSetConstantVibration(int integer) {
try {
TransactionBuilder builder = performInitialized("onSetConstantVibration");
JSONObject o = new JSONObject();
o.put("t", "vibrate");
o.put("n", integer);
uartTx(builder, "\u0010gb("+o.toString()+")\n");
builder.queue(getQueue());
} catch (Exception e) {
GB.toast(getContext(), "Error vibrating: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
uartTxJSON("onSetConstantVibration", o);
} catch (JSONException e) {
LOG.info("JSONException: " + e.getLocalizedMessage());
}
}
@ -387,7 +473,6 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
try {
TransactionBuilder builder = performInitialized("onSendWeather");
JSONObject o = new JSONObject();
o.put("t", "weather");
o.put("temp", weatherSpec.currentTemp);
@ -395,10 +480,9 @@ public class BangleJSDeviceSupport extends AbstractBTLEDeviceSupport {
o.put("txt", weatherSpec.currentCondition);
o.put("wind", weatherSpec.windSpeed);
o.put("loc", weatherSpec.location);
uartTx(builder, "\u0010gb("+o.toString()+")\n");
builder.queue(getQueue());
} catch (Exception e) {
GB.toast(getContext(), "Error showing weather: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
uartTxJSON("onSendWeather", o);
} catch (JSONException e) {
LOG.info("JSONException: " + e.getLocalizedMessage());
}
}
}