1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-07-01 17:26:18 +02:00
Gadgetbridge/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/waspos/WaspOSDeviceSupport.java
2023-06-13 12:06:13 +00:00

401 lines
16 KiB
Java

/* 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.text.format.DateFormat;
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.util.ArrayList;
import java.util.Calendar;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
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.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.setCallback(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": {
GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
if (json.has("bat")) {
int b = json.getInt("bat");
if (b < 0) b = 0;
if (b > 100) b = 100;
batteryInfo.level = b;
batteryInfo.state = BatteryState.BATTERY_NORMAL;
}
if (json.has("volt"))
batteryInfo.voltage = (float) json.getDouble("volt");
handleGBDeviceEvent(batteryInfo);
} 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) {
CharSequence formattedDate = DateFormat.format("(yyyy, MM, dd, HH, mm, ss)", new java.util.Date());
String cmd = "\u0010watch.rtc.set_localtime(" + formattedDate + ")\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 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 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 onSendWeather(WeatherSpec weatherSpec) {
try {
JSONObject o = new JSONObject();
o.put("t", "weather");
o.put("temp", weatherSpec.currentTemp);
o.put("hum", weatherSpec.currentHumidity);
o.put("code", weatherSpec.currentConditionCode);
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());
}
}
}