mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-12-26 02:25:50 +01:00
Merge pull request #445 from Freeyourgadget/liveview
Liveview: Initial support for Liveview devices
This commit is contained in:
commit
259eb51784
@ -19,6 +19,7 @@ need to create an account and transmit any of your data to the vendor's servers.
|
|||||||
* Mi Band, Mi Band 1A, Mi Band 1S
|
* Mi Band, Mi Band 1A, Mi Band 1S
|
||||||
* Mi Band 2
|
* Mi Band 2
|
||||||
* Vibratissimo (experimental)
|
* Vibratissimo (experimental)
|
||||||
|
* Liveview
|
||||||
|
|
||||||
## Features (Pebble)
|
## Features (Pebble)
|
||||||
|
|
||||||
@ -113,6 +114,12 @@ Known Issues:
|
|||||||
* If you use other apps like Mi Fit, and "bonding" with Gadgetbridge does not work, please
|
* If you use other apps like Mi Fit, and "bonding" with Gadgetbridge does not work, please
|
||||||
try to unpair the band in the other app and try again with Gadgetbridge.
|
try to unpair the band in the other app and try again with Gadgetbridge.
|
||||||
|
|
||||||
|
|
||||||
|
## Features (Liveview)
|
||||||
|
|
||||||
|
* set time (automatically upon connection)
|
||||||
|
* display notifications and vibrate
|
||||||
|
|
||||||
## Authors (in order of first code contribution)
|
## Authors (in order of first code contribution)
|
||||||
|
|
||||||
* Andreas Shimokawa
|
* Andreas Shimokawa
|
||||||
|
@ -0,0 +1,110 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.devices.liveview;
|
||||||
|
//Changed by Renze: Fixed brightness constants
|
||||||
|
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message constants reverse-engineered by Andrew de Quincey (<a
|
||||||
|
* href="http://adq.livejournal.com">http://adq.livejournal.com</a>).
|
||||||
|
*
|
||||||
|
* @author Robert <xperimental@solidproject.de>
|
||||||
|
*/
|
||||||
|
public final class LiveviewConstants {
|
||||||
|
|
||||||
|
public static Charset ENCODING = StandardCharsets.ISO_8859_1;
|
||||||
|
public static ByteOrder BYTE_ORDER = ByteOrder.BIG_ENDIAN;
|
||||||
|
|
||||||
|
public static final byte CLOCK_24H = 0;
|
||||||
|
public static final byte CLOCK_12H = 1;
|
||||||
|
|
||||||
|
public static final byte MSG_GETCAPS = 1;
|
||||||
|
public static final byte MSG_GETCAPS_RESP = 2;
|
||||||
|
|
||||||
|
public static final byte MSG_DISPLAYTEXT = 3;
|
||||||
|
public static final byte MSG_DISPLAYTEXT_ACK = 4;
|
||||||
|
|
||||||
|
public static final byte MSG_DISPLAYPANEL = 5;
|
||||||
|
public static final byte MSG_DISPLAYPANEL_ACK = 6;
|
||||||
|
|
||||||
|
public static final byte MSG_DEVICESTATUS = 7;
|
||||||
|
public static final byte MSG_DEVICESTATUS_ACK = 8;
|
||||||
|
|
||||||
|
public static final byte MSG_DISPLAYBITMAP = 19;
|
||||||
|
public static final byte MSG_DISPLAYBITMAP_ACK = 20;
|
||||||
|
|
||||||
|
public static final byte MSG_CLEARDISPLAY = 21;
|
||||||
|
public static final byte MSG_CLEARDISPLAY_ACK = 22;
|
||||||
|
|
||||||
|
public static final byte MSG_SETMENUSIZE = 23;
|
||||||
|
public static final byte MSG_SETMENUSIZE_ACK = 24;
|
||||||
|
|
||||||
|
public static final byte MSG_GETMENUITEM = 25;
|
||||||
|
public static final byte MSG_GETMENUITEM_RESP = 26;
|
||||||
|
|
||||||
|
public static final byte MSG_GETALERT = 27;
|
||||||
|
public static final byte MSG_GETALERT_RESP = 28;
|
||||||
|
|
||||||
|
public static final byte MSG_NAVIGATION = 29;
|
||||||
|
public static final byte MSG_NAVIGATION_RESP = 30;
|
||||||
|
|
||||||
|
public static final byte MSG_SETSTATUSBAR = 33;
|
||||||
|
public static final byte MSG_SETSTATUSBAR_ACK = 34;
|
||||||
|
|
||||||
|
public static final byte MSG_GETMENUITEMS = 35;
|
||||||
|
|
||||||
|
public static final byte MSG_SETMENUSETTINGS = 36;
|
||||||
|
public static final byte MSG_SETMENUSETTINGS_ACK = 37;
|
||||||
|
|
||||||
|
public static final byte MSG_GETTIME = 38;
|
||||||
|
public static final byte MSG_GETTIME_RESP = 39;
|
||||||
|
|
||||||
|
public static final byte MSG_SETLED = 40;
|
||||||
|
public static final byte MSG_SETLED_ACK = 41;
|
||||||
|
|
||||||
|
public static final byte MSG_SETVIBRATE = 42;
|
||||||
|
public static final byte MSG_SETVIBRATE_ACK = 43;
|
||||||
|
|
||||||
|
public static final byte MSG_ACK = 44;
|
||||||
|
|
||||||
|
public static final byte MSG_SETSCREENMODE = 64;
|
||||||
|
public static final byte MSG_SETSCREENMODE_ACK = 65;
|
||||||
|
|
||||||
|
public static final byte MSG_GETSCREENMODE = 66;
|
||||||
|
public static final byte MSG_GETSCREENMODE_RESP = 67;
|
||||||
|
|
||||||
|
public static final int DEVICESTATUS_OFF = 0;
|
||||||
|
public static final int DEVICESTATUS_ON = 1;
|
||||||
|
public static final int DEVICESTATUS_MENU = 2;
|
||||||
|
|
||||||
|
public static final byte RESULT_OK = 0;
|
||||||
|
public static final byte RESULT_ERROR = 1;
|
||||||
|
public static final byte RESULT_OOM = 2;
|
||||||
|
public static final byte RESULT_EXIT = 3;
|
||||||
|
public static final byte RESULT_CANCEL = 4;
|
||||||
|
|
||||||
|
public static final int NAVACTION_PRESS = 0;
|
||||||
|
public static final int NAVACTION_LONGPRESS = 1;
|
||||||
|
public static final int NAVACTION_DOUBLEPRESS = 2;
|
||||||
|
|
||||||
|
public static final int NAVTYPE_UP = 0;
|
||||||
|
public static final int NAVTYPE_DOWN = 1;
|
||||||
|
public static final int NAVTYPE_LEFT = 2;
|
||||||
|
public static final int NAVTYPE_RIGHT = 3;
|
||||||
|
public static final int NAVTYPE_SELECT = 4;
|
||||||
|
public static final int NAVTYPE_MENUSELECT = 5;
|
||||||
|
|
||||||
|
public static final int ALERTACTION_CURRENT = 0;
|
||||||
|
public static final int ALERTACTION_FIRST = 1;
|
||||||
|
public static final int ALERTACTION_LAST = 2;
|
||||||
|
public static final int ALERTACTION_NEXT = 3;
|
||||||
|
public static final int ALERTACTION_PREV = 4;
|
||||||
|
|
||||||
|
public static final int BRIGHTNESS_OFF = 49;
|
||||||
|
public static final int BRIGHTNESS_DIM = 50;
|
||||||
|
public static final int BRIGHTNESS_MAX = 51;
|
||||||
|
|
||||||
|
public static final String CLIENT_SOFTWARE_VERSION = "0.0.3";
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,105 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.devices.liveview;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||||
|
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 LiveviewCoordinator extends AbstractDeviceCoordinator {
|
||||||
|
@Override
|
||||||
|
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
|
||||||
|
String name = candidate.getDevice().getName();
|
||||||
|
if (name != null && name.startsWith("LiveView")) {
|
||||||
|
return DeviceType.LIVEVIEW;
|
||||||
|
}
|
||||||
|
return DeviceType.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DeviceType getDeviceType() {
|
||||||
|
return DeviceType.LIVEVIEW;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends Activity> getPairingActivity() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends Activity> getPrimaryActivity() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsActivityDataFetching() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsActivityTracking() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsScreenshots() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsAlarmConfiguration() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsHeartRateMeasurement(GBDevice device) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTapString() {
|
||||||
|
//TODO: changeme
|
||||||
|
return R.string.tap_connected_device_for_activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getManufacturer() {
|
||||||
|
return "Sony Ericsson";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsAppsManagement() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends Activity> getAppsManagementActivity() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
|
||||||
|
// nothing to delete, yet
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,7 @@ public enum DeviceType {
|
|||||||
MIBAND(10),
|
MIBAND(10),
|
||||||
MIBAND2(11),
|
MIBAND2(11),
|
||||||
VIBRATISSIMO(20),
|
VIBRATISSIMO(20),
|
||||||
|
LIVEVIEW(30),
|
||||||
TEST(1000);
|
TEST(1000);
|
||||||
|
|
||||||
private final int key;
|
private final int key;
|
||||||
|
@ -174,7 +174,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
|||||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||||
DaoSession session = dbHandler.getDaoSession();
|
DaoSession session = dbHandler.getDaoSession();
|
||||||
boolean askForDBMigration = false;
|
boolean askForDBMigration = false;
|
||||||
if (DBHelper.findDevice(device, session) == null && device.getType() != DeviceType.VIBRATISSIMO) {
|
if (DBHelper.findDevice(device, session) == null && device.getType() != DeviceType.VIBRATISSIMO && (device.getType() != DeviceType.LIVEVIEW)) {
|
||||||
askForDBMigration = true;
|
askForDBMigration = true;
|
||||||
}
|
}
|
||||||
DBHelper.getDevice(device, session); // implicitly creates the device in database if not present, and updates device attributes
|
DBHelper.getDevice(device, session); // implicitly creates the device in database if not present, and updates device attributes
|
||||||
|
@ -9,8 +9,8 @@ import java.util.EnumSet;
|
|||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.vibratissimo.VibratissimoCoordinator;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.liveview.LiveviewSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBand2Support;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBand2Support;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleSupport;
|
||||||
@ -93,6 +93,9 @@ public class DeviceSupportFactory {
|
|||||||
case VIBRATISSIMO:
|
case VIBRATISSIMO:
|
||||||
deviceSupport = new ServiceDeviceSupport(new VibratissimoSupport(), EnumSet.of(ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
deviceSupport = new ServiceDeviceSupport(new VibratissimoSupport(), EnumSet.of(ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||||
break;
|
break;
|
||||||
|
case LIVEVIEW:
|
||||||
|
deviceSupport = new ServiceDeviceSupport(new LiveviewSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (deviceSupport != null) {
|
if (deviceSupport != null) {
|
||||||
deviceSupport.setContext(gbDevice, mBtAdapter, mContext);
|
deviceSupport.setContext(gbDevice, mBtAdapter, mContext);
|
||||||
|
@ -0,0 +1,220 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.liveview;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothAdapter;
|
||||||
|
import android.bluetooth.BluetoothDevice;
|
||||||
|
import android.bluetooth.BluetoothSocket;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.ParcelUuid;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.liveview.LiveviewConstants;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
|
public class LiveviewIoThread extends GBDeviceIoThread {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(LiveviewIoThread.class);
|
||||||
|
|
||||||
|
private static final UUID SERIAL = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
|
||||||
|
|
||||||
|
private final LiveviewProtocol mLiveviewProtocol;
|
||||||
|
private final LiveviewSupport mLiveviewSupport;
|
||||||
|
|
||||||
|
|
||||||
|
private BluetoothAdapter mBtAdapter = null;
|
||||||
|
private BluetoothSocket mBtSocket = null;
|
||||||
|
private InputStream mInStream = null;
|
||||||
|
private OutputStream mOutStream = null;
|
||||||
|
private boolean mQuit = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void quit() {
|
||||||
|
mQuit = true;
|
||||||
|
if (mBtSocket != null) {
|
||||||
|
try {
|
||||||
|
mBtSocket.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean mIsConnected = false;
|
||||||
|
|
||||||
|
|
||||||
|
public LiveviewIoThread(GBDevice gbDevice, Context context, GBDeviceProtocol lvProtocol, LiveviewSupport lvSupport, BluetoothAdapter lvBtAdapter) {
|
||||||
|
super(gbDevice, context);
|
||||||
|
mLiveviewProtocol = (LiveviewProtocol) lvProtocol;
|
||||||
|
mBtAdapter = lvBtAdapter;
|
||||||
|
mLiveviewSupport = lvSupport;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void write(byte[] bytes) {
|
||||||
|
if (null == bytes)
|
||||||
|
return;
|
||||||
|
LOG.debug("writing:" + GB.hexdump(bytes, 0, bytes.length));
|
||||||
|
try {
|
||||||
|
mOutStream.write(bytes);
|
||||||
|
mOutStream.flush();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Error writing.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
mIsConnected = connect();
|
||||||
|
if (!mIsConnected) {
|
||||||
|
setUpdateState(GBDevice.State.NOT_CONNECTED);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mQuit = false;
|
||||||
|
|
||||||
|
while (!mQuit) {
|
||||||
|
LOG.info("Ready for a new message exchange.");
|
||||||
|
|
||||||
|
try {
|
||||||
|
GBDeviceEvent deviceEvents[] = mLiveviewProtocol.decodeResponse(parseIncoming());
|
||||||
|
if (deviceEvents == null) {
|
||||||
|
LOG.info("unhandled message");
|
||||||
|
} else {
|
||||||
|
for (GBDeviceEvent deviceEvent : deviceEvents) {
|
||||||
|
if (deviceEvent == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
mLiveviewSupport.evaluateGBDeviceEvent(deviceEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SocketTimeoutException ignore) {
|
||||||
|
LOG.debug("socket timeout, we can't help but ignore this");
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.info(e.getMessage());
|
||||||
|
mIsConnected = false;
|
||||||
|
mBtSocket = null;
|
||||||
|
mInStream = null;
|
||||||
|
mOutStream = null;
|
||||||
|
LOG.info("Bluetooth socket closed, will quit IO Thread");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mIsConnected = false;
|
||||||
|
if (mBtSocket != null) {
|
||||||
|
try {
|
||||||
|
mBtSocket.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error(e.getMessage());
|
||||||
|
}
|
||||||
|
mBtSocket = null;
|
||||||
|
}
|
||||||
|
setUpdateState(GBDevice.State.NOT_CONNECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean connect() {
|
||||||
|
GBDevice.State originalState = gbDevice.getState();
|
||||||
|
setUpdateState(GBDevice.State.CONNECTING);
|
||||||
|
|
||||||
|
try {
|
||||||
|
BluetoothDevice btDevice = mBtAdapter.getRemoteDevice(gbDevice.getAddress());
|
||||||
|
ParcelUuid uuids[] = btDevice.getUuids();
|
||||||
|
if (uuids == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (ParcelUuid uuid : uuids) {
|
||||||
|
LOG.info("found service UUID " + uuid);
|
||||||
|
}
|
||||||
|
mBtSocket = btDevice.createRfcommSocketToServiceRecord(uuids[0].getUuid());
|
||||||
|
mBtSocket.connect();
|
||||||
|
mInStream = mBtSocket.getInputStream();
|
||||||
|
mOutStream = mBtSocket.getOutputStream();
|
||||||
|
setUpdateState(GBDevice.State.CONNECTED);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Server socket cannot be started.");
|
||||||
|
//LOG.error(e.getMessage());
|
||||||
|
setUpdateState(originalState);
|
||||||
|
mInStream = null;
|
||||||
|
mOutStream = null;
|
||||||
|
mBtSocket = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
write(mLiveviewProtocol.encodeSetTime());
|
||||||
|
setUpdateState(GBDevice.State.INITIALIZED);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setUpdateState(GBDevice.State state) {
|
||||||
|
gbDevice.setState(state);
|
||||||
|
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] parseIncoming() throws IOException {
|
||||||
|
ByteArrayOutputStream msgStream = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
boolean finished = false;
|
||||||
|
ReaderState state = ReaderState.ID;
|
||||||
|
byte[] incoming = new byte[1];
|
||||||
|
|
||||||
|
while (!finished) {
|
||||||
|
mInStream.read(incoming);
|
||||||
|
msgStream.write(incoming);
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case ID:
|
||||||
|
state = ReaderState.HEADER_LEN;
|
||||||
|
incoming = new byte[1];
|
||||||
|
break;
|
||||||
|
case HEADER_LEN:
|
||||||
|
int headerSize = 0xff & incoming[0];
|
||||||
|
if (headerSize < 0)
|
||||||
|
throw new IOException();
|
||||||
|
state = ReaderState.HEADER;
|
||||||
|
incoming = new byte[headerSize];
|
||||||
|
break;
|
||||||
|
case HEADER:
|
||||||
|
int payloadSize = getLastInt(msgStream);
|
||||||
|
if (payloadSize < 0 || payloadSize > 8000) //this will possibly be changed in the future
|
||||||
|
throw new IOException();
|
||||||
|
state = ReaderState.PAYLOAD;
|
||||||
|
incoming = new byte[payloadSize];
|
||||||
|
break;
|
||||||
|
case PAYLOAD: //read is blocking, if we are here we have all the data
|
||||||
|
finished = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
byte[] msgArray = msgStream.toByteArray();
|
||||||
|
LOG.debug("received: " + GB.hexdump(msgArray, 0, msgArray.length));
|
||||||
|
return msgArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration containing the possible internal status of the reader.
|
||||||
|
*/
|
||||||
|
private enum ReaderState {
|
||||||
|
ID, HEADER_LEN, HEADER, PAYLOAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getLastInt(ByteArrayOutputStream stream) {
|
||||||
|
byte[] array = stream.toByteArray();
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(array, array.length - 4, 4);
|
||||||
|
buffer.order(LiveviewConstants.BYTE_ORDER);
|
||||||
|
return buffer.getInt();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,132 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.liveview;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Calendar;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.liveview.LiveviewConstants;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
|
||||||
|
|
||||||
|
public class LiveviewProtocol extends GBDeviceProtocol {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] encodeFindDevice(boolean start) {
|
||||||
|
return encodeVibrateRequest((short) 100, (short) 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected LiveviewProtocol(GBDevice device) {
|
||||||
|
super(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GBDeviceEvent[] decodeResponse(byte[] responseData) {
|
||||||
|
int length = responseData.length;
|
||||||
|
if (length < 4) {
|
||||||
|
//empty message
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(responseData, 0, length);
|
||||||
|
byte msgId = buffer.get();
|
||||||
|
buffer.get();
|
||||||
|
int payloadLen = buffer.getInt();
|
||||||
|
GBDeviceEventSendBytes reply = new GBDeviceEventSendBytes();
|
||||||
|
if (payloadLen + 6 == length) {
|
||||||
|
switch (msgId) {
|
||||||
|
case LiveviewConstants.MSG_DEVICESTATUS:
|
||||||
|
reply.encodedBytes = constructMessage(LiveviewConstants.MSG_DEVICESTATUS_ACK, new byte[]{LiveviewConstants.RESULT_OK});
|
||||||
|
break;
|
||||||
|
case LiveviewConstants.MSG_DISPLAYPANEL_ACK:
|
||||||
|
reply.encodedBytes = encodeVibrateRequest((short) 100, (short) 200); //hack to make the notifications vibrate!
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
GBDeviceEventSendBytes ack = new GBDeviceEventSendBytes();
|
||||||
|
ack.encodedBytes = constructMessage(LiveviewConstants.MSG_ACK, new byte[]{msgId});
|
||||||
|
|
||||||
|
return new GBDeviceEvent[]{ack, reply};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return super.decodeResponse(responseData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] encodeSetTime() {
|
||||||
|
int time = (int) (Calendar.getInstance().getTimeInMillis() / 1000);
|
||||||
|
time += Calendar.getInstance().get(Calendar.ZONE_OFFSET) / 1000;
|
||||||
|
time += Calendar.getInstance().get(Calendar.DST_OFFSET) / 1000;
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(5);
|
||||||
|
buffer.order(LiveviewConstants.BYTE_ORDER);
|
||||||
|
buffer.putInt(time);
|
||||||
|
buffer.put(LiveviewConstants.CLOCK_24H);
|
||||||
|
return constructMessage(LiveviewConstants.MSG_GETTIME_RESP, buffer.array());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] encodeNotification(NotificationSpec notificationSpec) {
|
||||||
|
String headerText;
|
||||||
|
// for SMS and EMAIL that came in though SMS or K9 receiver
|
||||||
|
if (notificationSpec.sender != null) {
|
||||||
|
headerText = notificationSpec.sender;
|
||||||
|
} else {
|
||||||
|
headerText = notificationSpec.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
String footerText = (null != notificationSpec.sourceName) ? notificationSpec.sourceName : "";
|
||||||
|
String bodyText = (null != notificationSpec.body) ? notificationSpec.body : "";
|
||||||
|
|
||||||
|
byte[] headerTextArray = headerText.getBytes(LiveviewConstants.ENCODING);
|
||||||
|
byte[] footerTextArray = footerText.getBytes(LiveviewConstants.ENCODING);
|
||||||
|
byte[] bodyTextArray = bodyText.getBytes(LiveviewConstants.ENCODING);
|
||||||
|
int size = 15 + headerTextArray.length + bodyTextArray.length + footerTextArray.length;
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(size);
|
||||||
|
buffer.put((byte) 1);
|
||||||
|
buffer.putShort((short) 0);
|
||||||
|
buffer.putShort((short) 0);
|
||||||
|
buffer.putShort((short) 0);
|
||||||
|
buffer.put((byte) 80); //should alert but it doesn't make the liveview vibrate
|
||||||
|
|
||||||
|
buffer.put((byte) 0); //0 is for plaintext vs bitmapimage (1) strings
|
||||||
|
buffer.putShort((short) headerTextArray.length);
|
||||||
|
buffer.put(headerTextArray);
|
||||||
|
buffer.putShort((short) bodyTextArray.length);
|
||||||
|
buffer.put(bodyTextArray);
|
||||||
|
buffer.putShort((short) footerTextArray.length);
|
||||||
|
buffer.put(footerTextArray);
|
||||||
|
return constructMessage(LiveviewConstants.MSG_DISPLAYPANEL, buffer.array());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//specific messages
|
||||||
|
|
||||||
|
public static byte[] constructMessage(byte messageType, byte[] payload) {
|
||||||
|
ByteBuffer msgBuffer = ByteBuffer.allocate(payload.length + 6);
|
||||||
|
msgBuffer.order(LiveviewConstants.BYTE_ORDER);
|
||||||
|
msgBuffer.put(messageType);
|
||||||
|
msgBuffer.put((byte) 4);
|
||||||
|
msgBuffer.putInt(payload.length);
|
||||||
|
msgBuffer.put(payload);
|
||||||
|
return msgBuffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] encodeVibrateRequest(short delay, short time) {
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(4);
|
||||||
|
buffer.order(LiveviewConstants.BYTE_ORDER);
|
||||||
|
buffer.putShort(delay);
|
||||||
|
buffer.putShort(time);
|
||||||
|
return constructMessage(LiveviewConstants.MSG_SETVIBRATE, buffer.array());
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] encodeCapabilitiesRequest() {
|
||||||
|
byte[] version = LiveviewConstants.CLIENT_SOFTWARE_VERSION.getBytes(LiveviewConstants.ENCODING);
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(version.length + 1);
|
||||||
|
buffer.order(LiveviewConstants.BYTE_ORDER);
|
||||||
|
buffer.put((byte) version.length);
|
||||||
|
buffer.put(version);
|
||||||
|
return constructMessage(LiveviewConstants.MSG_GETCAPS, buffer.array());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,106 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.liveview;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
|
||||||
|
|
||||||
|
public class LiveviewSupport extends AbstractSerialDeviceSupport {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean connect() {
|
||||||
|
getDeviceIOThread().start();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected GBDeviceProtocol createDeviceProtocol() {
|
||||||
|
return new LiveviewProtocol(getDevice());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected GBDeviceIoThread createDeviceIOThread() {
|
||||||
|
return new LiveviewIoThread(getDevice(), getContext(), getDeviceProtocol(), LiveviewSupport.this, getBluetoothAdapter());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean useAutoConnect() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInstallApp(Uri uri) {
|
||||||
|
//nothing to do ATM
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAppConfiguration(UUID uuid, String config) {
|
||||||
|
//nothing to do ATM
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHeartRateTest() {
|
||||||
|
//nothing to do ATM
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetConstantVibration(int intensity) {
|
||||||
|
//nothing to do ATM
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized LiveviewIoThread getDeviceIOThread() {
|
||||||
|
return (LiveviewIoThread) super.getDeviceIOThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNotification(NotificationSpec notificationSpec) {
|
||||||
|
super.onNotification(notificationSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetCallState(CallSpec callSpec) {
|
||||||
|
//nothing to do ATM
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetMusicState(MusicStateSpec musicStateSpec) {
|
||||||
|
//nothing to do ATM
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetMusicInfo(MusicSpec musicSpec) {
|
||||||
|
//nothing to do ATM
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
|
||||||
|
//nothing to do ATM
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
|
||||||
|
//nothing to do ATM
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDeleteCalendarEvent(byte type, long id) {
|
||||||
|
//nothing to do ATM
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTestNewFunction() {
|
||||||
|
//nothing to do ATM
|
||||||
|
}
|
||||||
|
}
|
@ -22,6 +22,7 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.UnknownDeviceCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.UnknownDeviceCoordinator;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.liveview.LiveviewCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Coordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Coordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator;
|
||||||
@ -165,6 +166,7 @@ public class DeviceHelper {
|
|||||||
result.add(new MiBandCoordinator());
|
result.add(new MiBandCoordinator());
|
||||||
result.add(new PebbleCoordinator());
|
result.add(new PebbleCoordinator());
|
||||||
result.add(new VibratissimoCoordinator());
|
result.add(new VibratissimoCoordinator());
|
||||||
|
result.add(new LiveviewCoordinator());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user