/* Copyright (C) 2016-2024 Alberto, Andreas Shimokawa, Arjan Schrijver,
Carsten Pfeiffer, Damien Gaignon, ivanovlev, João Paulo Barraca, Lesur
Frederic, Pavel Motyrev, Quallenauge, Sebastian Kranz
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 . */
package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus;
/*
* @author João Paulo Barraca <jpbarraca@gmail.com>
*/
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.widget.Toast;
import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusConstants;
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusWeatherCode;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
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.service.btle.profiles.deviceinfo.DeviceInfo;
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.CurrentPosition;
import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class HPlusSupport extends AbstractBTLEDeviceSupport {
private static final Logger LOG = LoggerFactory.getLogger(HPlusSupport.class);
private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
public BluetoothGattCharacteristic ctrlCharacteristic = null;
public BluetoothGattCharacteristic measureCharacteristic = null;
private HPlusHandlerThread syncHelper;
private DeviceType deviceType = DeviceType.UNKNOWN;
public HPlusSupport(DeviceType type) {
super(LOG);
LOG.info("HPlusSupport Instance Created");
deviceType = type;
addSupportedService(HPlusConstants.UUID_SERVICE_HP);
}
@Override
public void dispose() {
LOG.info("Dispose");
close();
super.dispose();
}
@Override
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
LOG.info("Initializing");
gbDevice.setState(GBDevice.State.INITIALIZING);
gbDevice.sendDeviceUpdateIntent(getContext());
measureCharacteristic = getCharacteristic(HPlusConstants.UUID_CHARACTERISTIC_MEASURE);
ctrlCharacteristic = getCharacteristic(HPlusConstants.UUID_CHARACTERISTIC_CONTROL);
builder.notify(getCharacteristic(HPlusConstants.UUID_CHARACTERISTIC_MEASURE), true);
builder.setCallback(this);
builder.notify(measureCharacteristic, true);
//Initialize device
sendUserInfo(builder); //Sync preferences
gbDevice.setState(GBDevice.State.INITIALIZED);
gbDevice.sendDeviceUpdateIntent(getContext());
if (syncHelper == null) {
syncHelper = new HPlusHandlerThread(getDevice(), getContext(), this);
syncHelper.start();
}
syncHelper.sync();
getDevice().setFirmwareVersion("N/A");
getDevice().setFirmwareVersion2("N/A");
requestDeviceInfo(builder);
LOG.info("Initialization Done");
return builder;
}
private HPlusSupport sendUserInfo(TransactionBuilder builder) {
builder.write(ctrlCharacteristic, HPlusConstants.CMD_SET_PREF_START);
builder.write(ctrlCharacteristic, HPlusConstants.CMD_SET_PREF_START1);
syncPreferences(builder);
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_CONF_END});
return this;
}
private HPlusSupport syncPreferences(TransactionBuilder transaction) {
if (deviceType == DeviceType.HPLUS || deviceType == DeviceType.EXRIZUK8) {
setSIT(transaction); //Sync SIT Interval
}
setCurrentDate(transaction);
setCurrentTime(transaction);
setDayOfWeek(transaction);
setTimeMode(transaction);
setGender(transaction);
setAge(transaction);
setWeight(transaction);
setHeight(transaction);
setGoal(transaction);
setLanguage(transaction);
setScreenTime(transaction);
setUnit(transaction);
setAllDayHeart(transaction);
return this;
}
private HPlusSupport setLanguage(TransactionBuilder transaction) {
byte value = HPlusCoordinator.getLanguage(getDevice().getAddress());
transaction.write(ctrlCharacteristic, new byte[]{
HPlusConstants.CMD_SET_LANGUAGE,
value
});
return this;
}
public byte getTimeMode() {
if ("24h".equals(getDevicePrefs().getTimeFormat())) {
return HPlusConstants.ARG_TIMEMODE_24H;
} else {
return HPlusConstants.ARG_TIMEMODE_12H;
}
}
private HPlusSupport setTimeMode(TransactionBuilder transaction) {
transaction.write(ctrlCharacteristic, new byte[]{
HPlusConstants.CMD_SET_TIMEMODE,
getTimeMode()
});
return this;
}
private HPlusSupport setUnit(TransactionBuilder transaction) {
byte value = HPlusCoordinator.getUnit(getDevice().getAddress());
transaction.write(ctrlCharacteristic, new byte[]{
HPlusConstants.CMD_SET_UNITS,
value
});
return this;
}
private HPlusSupport setCurrentDate(TransactionBuilder transaction) {
Calendar c = GregorianCalendar.getInstance();
int year = c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH);
int day = c.get(Calendar.DAY_OF_MONTH);
transaction.write(ctrlCharacteristic, new byte[]{
HPlusConstants.CMD_SET_DATE,
(byte) ((year / 256) & 0xff),
(byte) (year % 256),
(byte) (month + 1),
(byte) (day)
});
return this;
}
private HPlusSupport setCurrentTime(TransactionBuilder transaction) {
Calendar c = GregorianCalendar.getInstance();
transaction.write(ctrlCharacteristic, new byte[]{
HPlusConstants.CMD_SET_TIME,
(byte) c.get(Calendar.HOUR_OF_DAY),
(byte) c.get(Calendar.MINUTE),
(byte) c.get(Calendar.SECOND)
});
return this;
}
private HPlusSupport setDayOfWeek(TransactionBuilder transaction) {
Calendar c = GregorianCalendar.getInstance();
transaction.write(ctrlCharacteristic, new byte[]{
HPlusConstants.CMD_SET_WEEK,
(byte) (c.get(Calendar.DAY_OF_WEEK) - 1)
});
return this;
}
private HPlusSupport setSIT(TransactionBuilder transaction) {
//Makibes F68 doesn't like this command.
//Just ignore.
if (deviceType == DeviceType.MAKIBESF68) {
return this;
}
int startTime = HPlusCoordinator.getSITStartTime(getDevice().getAddress());
int endTime = HPlusCoordinator.getSITEndTime(getDevice().getAddress());
Calendar now = GregorianCalendar.getInstance();
transaction.write(ctrlCharacteristic, new byte[]{
HPlusConstants.CMD_SET_SIT_INTERVAL,
(byte) ((startTime / 256) & 0xff),
(byte) (startTime % 256),
(byte) ((endTime / 256) & 0xff),
(byte) (endTime % 256),
0,
0,
(byte) ((now.get(Calendar.YEAR) / 256) & 0xff),
(byte) (now.get(Calendar.YEAR) % 256),
(byte) (now.get(Calendar.MONTH) + 1),
(byte) (now.get(Calendar.DAY_OF_MONTH)),
(byte) (now.get(Calendar.HOUR_OF_DAY)),
(byte) (now.get(Calendar.MINUTE)),
(byte) (now.get(Calendar.SECOND)),
0,
0,
0,
0
});
return this;
}
private HPlusSupport setWeight(TransactionBuilder transaction) {
byte value = HPlusCoordinator.getUserWeight();
transaction.write(ctrlCharacteristic, new byte[]{
HPlusConstants.CMD_SET_WEIGHT,
value
});
return this;
}
private HPlusSupport setHeight(TransactionBuilder transaction) {
byte value = HPlusCoordinator.getUserHeight();
transaction.write(ctrlCharacteristic, new byte[]{
HPlusConstants.CMD_HEIGHT,
value
});
return this;
}
private HPlusSupport setAge(TransactionBuilder transaction) {
byte value = HPlusCoordinator.getUserAge();
transaction.write(ctrlCharacteristic, new byte[]{
HPlusConstants.CMD_SET_AGE,
value
});
return this;
}
private HPlusSupport setGender(TransactionBuilder transaction) {
byte value = HPlusCoordinator.getUserGender();
transaction.write(ctrlCharacteristic, new byte[]{
HPlusConstants.CMD_SET_GENDER,
value
});
return this;
}
private HPlusSupport setGoal(TransactionBuilder transaction) {
int value = HPlusCoordinator.getGoal();
transaction.write(ctrlCharacteristic, new byte[]{
HPlusConstants.CMD_SET_GOAL,
(byte) ((value / 256) & 0xff),
(byte) (value % 256)
});
return this;
}
public byte getScreenTime() {
return (byte) (getDevicePrefs().getInt(HPlusConstants.PREF_HPLUS_SCREENTIME, 5) & 0xFF);
}
private HPlusSupport setScreenTime(TransactionBuilder transaction) {
transaction.write(ctrlCharacteristic, new byte[]{
HPlusConstants.CMD_SET_SCREENTIME,
getScreenTime()
});
return this;
}
public byte getAllDayHR() {
boolean value = (getDevicePrefs().getBoolean(HPlusConstants.PREF_HPLUS_ALLDAYHR, true));
if (value) {
return HPlusConstants.ARG_HEARTRATE_ALLDAY_ON;
} else {
return HPlusConstants.ARG_HEARTRATE_ALLDAY_OFF;
}
}
private HPlusSupport setAllDayHeart(TransactionBuilder transaction) {
transaction.write(ctrlCharacteristic, new byte[]{
HPlusConstants.CMD_SET_ALLDAY_HRM,
getAllDayHR()
});
return this;
}
private HPlusSupport setAlarm(TransactionBuilder transaction, Calendar t) {
byte hour = HPlusConstants.ARG_ALARM_DISABLE;
byte minute = HPlusConstants.ARG_ALARM_DISABLE;
if (t != null) {
hour = (byte) t.get(Calendar.HOUR_OF_DAY);
minute = (byte) t.get(Calendar.MINUTE);
}
transaction.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_ALARM,
hour,
minute});
return this;
}
private HPlusSupport setFindMe(TransactionBuilder transaction, boolean state) {
//TODO: Find how this works
byte[] msg = new byte[2];
msg[0] = HPlusConstants.CMD_SET_FINDME;
if (state)
msg[1] = HPlusConstants.ARG_FINDME_ON;
else
msg[1] = HPlusConstants.ARG_FINDME_OFF;
transaction.write(ctrlCharacteristic, msg);
return this;
}
private HPlusSupport requestDeviceInfo(TransactionBuilder builder) {
// HPlus devices seem to report some information in an alternative manner
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_DEVICE_ID});
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_VERSION});
return this;
}
@Override
public boolean useAutoConnect() {
return true;
}
private void handleDeviceInfo(DeviceInfo info) {
LOG.warn("Device info: " + info);
}
@Override
public void onNotification(NotificationSpec notificationSpec) {
//TODO: Show different notifications according to source as Band supports this
//LOG.info("OnNotification: Title: "+notificationSpec.title+" Body: "+notificationSpec.body+" Source: "+notificationSpec.sourceName+" Sender: "+notificationSpec.sender+" Subject: "+notificationSpec.subject);
showText(notificationSpec.title, notificationSpec.body);
}
@Override
public void onSetTime() {
try {
TransactionBuilder builder = performInitialized("time");
setCurrentDate(builder);
setCurrentTime(builder);
builder.queue(getQueue());
} catch (IOException e) {
}
}
@Override
public void onSetAlarms(ArrayList extends Alarm> alarms) {
try {
TransactionBuilder builder = performInitialized("alarm");
for (Alarm alarm : alarms) {
if (!alarm.getEnabled())
continue;
if (alarm.getSmartWakeup()) //Not available
continue;
Calendar t = AlarmUtils.toCalendar(alarm);
setAlarm(builder, t);
builder.queue(getQueue());
GB.toast(getContext(), getContext().getString(R.string.user_feedback_miband_set_alarms_ok), Toast.LENGTH_SHORT, GB.INFO);
return; //Only first alarm
}
setAlarm(builder, null);
builder.queue(getQueue());
GB.toast(getContext(), getContext().getString(R.string.user_feedback_all_alarms_disabled), Toast.LENGTH_SHORT, GB.INFO);
} catch (Exception e) {
}
}
@Override
public void onSetCallState(CallSpec callSpec) {
switch (callSpec.command) {
case CallSpec.CALL_INCOMING: {
showIncomingCall(callSpec.name, callSpec.number);
break;
}
}
}
@Override
public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
LOG.info("Canned Messages: " + cannedMessagesSpec);
}
@Override
public void onEnableRealtimeSteps(boolean enable) {
onEnableRealtimeHeartRateMeasurement(enable);
}
@Override
public void onFetchRecordedData(int dataTypes) {
if (syncHelper == null) {
syncHelper = new HPlusHandlerThread(gbDevice, getContext(), this);
syncHelper.start();
}
syncHelper.sync();
}
@Override
public void onReset(int flags) {
try {
getQueue().clear();
TransactionBuilder builder = performInitialized("Shutdown");
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SHUTDOWN, HPlusConstants.ARG_SHUTDOWN_EN});
builder.queue(getQueue());
} catch (Exception e) {
}
}
@Override
public void onHeartRateTest() {
getQueue().clear();
try {
TransactionBuilder builder = performInitialized("HeartRateTest");
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_HEARTRATE_STATE, HPlusConstants.ARG_HEARTRATE_MEASURE_ON}); //Set Real Time... ?
builder.queue(getQueue());
} catch (Exception e) {
}
}
@Override
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
try {
TransactionBuilder builder = performInitialized("realTimeHeartMeasurement");
byte state;
if (enable)
state = HPlusConstants.ARG_HEARTRATE_ALLDAY_ON;
else
state = HPlusConstants.ARG_HEARTRATE_ALLDAY_OFF;
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_ALLDAY_HRM, state});
builder.queue(getQueue());
} catch (Exception e) {
}
}
@Override
public void onFindDevice(boolean start) {
try {
TransactionBuilder builder = performInitialized("findMe");
setFindMe(builder, start);
builder.queue(getQueue());
} catch (IOException e) {
GB.toast(getContext(), "Error toggling Find Me: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
}
}
@Override
public void onSetConstantVibration(int intensity) {
try {
TransactionBuilder builder = performInitialized("vibration");
byte[] msg = new byte[15];
msg[0] = HPlusConstants.CMD_SET_INCOMING_CALL_NUMBER;
for (int i = 0; i < msg.length - 1; i++)
msg[i + 1] = (byte) "Gadgetbridge".charAt(i);
builder.write(ctrlCharacteristic, msg);
builder.queue(getQueue());
} catch (IOException e) {
GB.toast(getContext(), "Error setting Vibration: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
}
}
@Override
public void onEnableHeartRateSleepSupport(boolean enable) {
onEnableRealtimeHeartRateMeasurement(enable);
}
@Override
public void onSendConfiguration(String config) {
TransactionBuilder builder;
try {
builder = performInitialized("Sending configuration for option: " + config);
switch (config) {
case SettingsActivity.PREF_MEASUREMENT_SYSTEM:
setUnit(builder);
break;
}
builder.queue(getQueue());
} catch (IOException e) {
GB.toast("Error setting configuration", Toast.LENGTH_LONG, GB.ERROR, e);
}
}
@Override
public void onTestNewFunction() {
LOG.info("Test New Function");
}
@Override
public void onSendWeather(ArrayList weatherSpecs) {
WeatherSpec weatherSpec = weatherSpecs.get(0);
try {
TransactionBuilder builder = performInitialized("sendWeather");
int windSpeed = (int) weatherSpec.windSpeed;
CurrentPosition currentPosition = new CurrentPosition();
int altitude = 0;
if (currentPosition.getLastKnownLocation() != null) {
altitude = (int) currentPosition.getLastKnownLocation().getAltitude();
}
int weatherCode = HPlusWeatherCode.mapOpenWeatherConditionToHPlusCondition(weatherSpec.currentConditionCode);
LOG.info("[WEATHER] currentConditionCode={} altitude={} temp={}", weatherCode, altitude, weatherSpec.currentTemp);
byte[] weatherInfo = new byte[]{(byte) HPlusConstants.CMD_SET_WEATHER_STATE,
(byte) ((weatherCode >> 8) & 255),
(byte) (weatherCode & 255),
(byte) weatherSpec.windDirection, (byte) 0, // weatherSpec.getWinPower(),
(byte) ((windSpeed >> 8) & 255),
(byte) (windSpeed & 255),
(byte) (weatherSpec.currentTemp - 17),
// base temperature information start at 17d celsius
(byte) (weatherSpec.todayMaxTemp - 17), // base temperature information start at 18d celsius
(byte) (weatherSpec.todayMinTemp - 17), // base temperature information start at 18d celsius
(byte) 0, // Life Index always 0
(byte) 0, // (byte) (weatherSpec.getPressure() & 255),
(byte) 0, // (byte) ((weatherSpec.getPressure() >> 8) & 255),
(byte) 0, // (byte) ((weatherSpec.getPressure() >> 16) & 255),
(byte) 0, // (byte) ((weatherSpec.getPressure() >> 24) & 255),
(byte) (byte) (altitude & 255),
(byte) (byte) ((altitude >> 8) & 255),
(byte) (byte) ((altitude >> 16) & 255),
(byte) (byte) ((altitude >> 24) & 255),
(byte) 0 // (byte)
};
builder.write(ctrlCharacteristic, weatherInfo);
builder.queue(getQueue());
} catch (IOException e) {
GB.toast(getContext(), "Error toggling Send Weather: " + e.getLocalizedMessage(), Toast.LENGTH_LONG,
GB.ERROR);
}
}
public void setUnicodeSupport(boolean support) {
HPlusCoordinator.setUnicodeSupport(gbDevice.getAddress(), support);
}
private void showIncomingCall(String name, String rawNumber) {
try {
TransactionBuilder builder = performInitialized("incomingCall");
//Enable call notifications
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_ACTION_INCOMING_CALL, 1});
//Show Call Icon
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_INCOMING_CALL, HPlusConstants.ARG_INCOMING_CALL});
builder.write(ctrlCharacteristic,
new byte[]{HPlusConstants.CMD_SET_INCOMING_CALL, HPlusConstants.ARG_INCOMING_CALL});
byte space = encodeStringToDevice(" ")[0];
if (name != null) {
byte[] msg = new byte[13];
// Show call name
for (int i = 0; i < msg.length; i++)
msg[i] = space;
byte[] nameBytes = encodeStringToDevice(name);
for (int i = 0; i < nameBytes.length && i < (msg.length - 1); i++)
msg[i + 1] = nameBytes[i];
msg[0] = HPlusConstants.CMD_ACTION_DISPLAY_TEXT_NAME;
builder.write(ctrlCharacteristic, msg);
msg[0] = HPlusConstants.CMD_ACTION_DISPLAY_TEXT_NAME_CN;
builder.write(ctrlCharacteristic, msg);
}
if (rawNumber != null) {
StringBuilder number = new StringBuilder();
//Clean up number as the device only accepts digits
for (char c : rawNumber.toCharArray()) {
if (Character.isDigit(c)) {
number.append(c);
}
}
byte[] msg = new byte[13];
//Show call number
for (int i = 0; i < msg.length; i++)
msg[i] = space;
for (int i = 0; i < number.length() && i < (msg.length - 1); i++)
msg[i + 1] = (byte) number.charAt(i);
msg[0] = HPlusConstants.CMD_SET_INCOMING_CALL_NUMBER;
builder.wait(200);
builder.write(ctrlCharacteristic, msg);
}
builder.queue(getQueue());
} catch (IOException e) {
GB.toast(getContext(), "Error showing incoming call: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
}
}
private void showText(String title, String body) {
try {
TransactionBuilder builder = performInitialized("notification");
String message = "";
if (title != null && title.length() > 0) {
message += title + " : ";
//message = StringUtils.pad(StringUtils.truncate(title, 16), 16); // Limit title to top row
}
if (body != null) {
message += body;
}
byte[] messageBytes = encodeStringToDevice(message);
int length = messageBytes.length / 17;
int linesNumbers = HPlusCoordinator.getNotificationLinesNumber(this.gbDevice.getAddress());
length = length > linesNumbers ? linesNumbers : length;
LOG.info("msglength:" + messageBytes.length + " length:" + length + " linesNumbers:" + linesNumbers);
if (HPlusCoordinator.getDisplayIncomingMessageIcon(this.gbDevice.getAddress()))
builder.write(ctrlCharacteristic,
new byte[]{HPlusConstants.CMD_SET_INCOMING_MESSAGE, HPlusConstants.ARG_INCOMING_MESSAGE});
int remaining = Math.min(255, (messageBytes.length % 17 > 0) ? length + 1 : length);
byte[] msgSpace = new byte[20];
byte[] msg;
Arrays.fill(msgSpace, encodeStringToDevice(" ")[0]);
msgSpace[0] = HPlusConstants.CMD_ACTION_DISPLAY_TEXT;
msgSpace[1] = (byte) remaining;
int message_index = 0;
int i = 3;
msg = msgSpace.clone();
for (int j = 0; j < messageBytes.length; j++) {
msg[i++] = messageBytes[j];
if (i == msg.length) {
message_index++;
msg[2] = (byte) message_index;
builder.write(ctrlCharacteristic, msg);
msg = msgSpace.clone();
if (message_index < remaining)
i = 3;
else
break;
}
}
msg[2] = (byte) remaining;
builder.write(ctrlCharacteristic, msg);
builder.queue(getQueue());
} catch (IOException e) {
GB.toast(getContext(), "Error showing device Notification: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
}
}
private void close() {
if (syncHelper != null) {
syncHelper.quit();
syncHelper.interrupt();
syncHelper = null;
}
}
/**
* HPlus devices accept a subset of GB2312 with some modifications.
* This function will apply a custom transliteration.
* While related to the methods implemented in LanguageUtils. These are specific for HPLUS
*
* @param s The String to transliterate
* @return An array of bytes ready to be sent to the device
*/
private byte[] encodeStringToDevice(String s) {
List outBytes = new ArrayList();
boolean unicode = HPlusCoordinator.getUnicodeSupport(this.gbDevice.getAddress());
LOG.info("Encode String: Unicode=" + unicode);
if (unicode) {
try {
return s.getBytes("Unicode");
} catch (UnsupportedEncodingException e) {
// Fallback. Result string may be strange, but better than nothing
LOG.error("Could not convert String to Bytes: " + e.getMessage());
return s.getBytes();
}
}
for (int i = 0; i < s.length(); i++) {
Character c = s.charAt(i);
byte[] cs;
if (HPlusConstants.transliterateMap.containsKey(c)) {
cs = HPlusConstants.transliterateMap.get(c);
} else {
try {
cs = c.toString().getBytes("GB2312");
} catch (UnsupportedEncodingException e) {
//Fallback. Result string may be strange, but better than nothing
cs = c.toString().getBytes();
LOG.error("Could not convert String to Bytes: " + e.getMessage());
}
}
for (int j = 0; j < cs.length; j++)
outBytes.add(cs[j]);
}
return ArrayUtils.toPrimitive(outBytes.toArray(new Byte[0]));
}
@Override
public boolean onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
if (super.onCharacteristicChanged(gatt, characteristic)) {
return true;
}
UUID characteristicUUID = characteristic.getUuid();
byte[] data = characteristic.getValue();
if (data.length == 0)
return true;
switch (data[0]) {
case HPlusConstants.DATA_VERSION:
case HPlusConstants.DATA_VERSION1:
return syncHelper.processVersion(data);
case HPlusConstants.DATA_STATS:
boolean result = syncHelper.processRealtimeStats(data, HPlusCoordinator.getUserAge());
if (result) {
processExtraInfo(data);
}
return result;
case HPlusConstants.DATA_SLEEP:
return syncHelper.processIncomingSleepData(data);
case HPlusConstants.DATA_STEPS:
return syncHelper.processDaySummary(data);
case HPlusConstants.DATA_DAY_SUMMARY:
case HPlusConstants.DATA_DAY_SUMMARY_ALT:
return syncHelper.processIncomingDaySlotData(data, HPlusCoordinator.getUserAge());
case HPlusConstants.DATA_UNKNOWN:
return true;
default:
LOG.info("Unhandled characteristic change: " + characteristicUUID + " code: " + Arrays.toString(data));
return true;
}
}
private void processExtraInfo(byte[] data) {
try {
HPlusDataRecordRealtime record = new HPlusDataRecordRealtime(data, HPlusCoordinator.getUserAge());
handleBatteryInfo(record.battery);
String DEVINFO_STEP = getContext().getString(R.string.chart_steps) + ": ";
String DEVINFO_DISTANCE = getContext().getString(R.string.distance) + ": ";
String DEVINFO_CALORY = getContext().getString(R.string.calories) + ": ";
String DEVINFO_HEART = getContext().getString(R.string.charts_legend_heartrate) + ": ";
String info = "";
if (record.steps > 0) {
info += DEVINFO_STEP + String.valueOf(record.steps) + " ";
}
if (record.distance > 0) {
info += DEVINFO_DISTANCE + String.valueOf(record.distance) + " ";
}
if (record.calories > 0) {
info += DEVINFO_CALORY + String.valueOf(record.calories) + " ";
}
if (record.heartRate > 0) {
info += DEVINFO_HEART + String.valueOf(record.heartRate) + " ";
}
if (!info.equals("")) {
getDevice().addDeviceInfo(new GenericItem("", info));
}
} catch (IllegalArgumentException e) {
LOG.info((e.getMessage()));
}
}
private void handleBatteryInfo(byte data) {
if (batteryCmd.level != (short) data) {
batteryCmd.level = (short) data;
handleGBDeviceEvent(batteryCmd);
}
}
@Override
public boolean getImplicitCallbackModify() {
return true;
}
@Override
public boolean getSendWriteRequestResponse() {
return false;
}
}