Skip to content

Instantly share code, notes, and snippets.

@freman
Created November 28, 2018 00:02
Show Gist options
  • Save freman/803c3e7c2bbd40303ca4cd680da62721 to your computer and use it in GitHub Desktop.
Save freman/803c3e7c2bbd40303ca4cd680da62721 to your computer and use it in GitHub Desktop.
Scales
package com.health.openscale.core.bluetooth;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.content.Context;
import com.health.openscale.R;
import com.health.openscale.core.OpenScale;
import com.health.openscale.core.datatypes.ScaleMeasurement;
import com.health.openscale.core.datatypes.ScaleUser;
import com.health.openscale.core.utils.Converters;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.TreeSet;
import java.util.UUID;
import timber.log.Timber;
public class BluetoothSenssunFat extends BluetoothCommunication {
private final UUID MODEL_A_MEASUREMENT_SERVICE = UUID.fromString("0000fff0-0000-1000-8000-00805f9b34fb");
private final UUID MODEL_A_NOTIFICATION_CHARACTERISTIC = UUID.fromString("0000fff1-0000-1000-8000-00805f9b34fb");
private final UUID MODEL_A_WRITE_CHARACTERISTIC = UUID.fromString("0000fff2-0000-1000-8000-00805f9b34fb");
private final UUID MODEL_B_MEASUREMENT_SERVICE = UUID.fromString("0000ffb0-0000-1000-8000-00805f9b34fb");
private final UUID MODEL_B_NOTIFICATION_CHARACTERISTIC = UUID.fromString("0000ffb2-0000-1000-8000-00805f9b34fb");
private final UUID MODEL_B_WRITE_CHARACTERISTIC = UUID.fromString("0000ffb2-0000-1000-8000-00805f9b34fb");
private final UUID WEIGHT_MEASUREMENT_CONFIG = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
private static final byte[] SynchronizeDateBuffer = new byte[]{-91, 48, 0, 0, 0, 0, 0, 0, 0};
private static final byte[] SynchronizeTimeBuffer = new byte[]{-91, 49, 0, 0, 0, 0, 0, 0, 0};
private UUID measurementService;
private UUID writeCharacteristic;
private int lastWeight, lastFat, lastHydration, lastMuscle, lastBone, lastKcal;
private boolean weightStabilized, stepMessageDisplayed;
private int values;
public BluetoothSenssunFat(Context context) {
super(context);
}
@Override
public String driverName() {
return "Senssun Fat";
}
@Override
protected boolean nextInitCmd(int stateNr) {
switch (stateNr) {
case 0:
weightStabilized = false;
stepMessageDisplayed = false;
values = 0;
for (BluetoothGattService gattService : getBluetoothGattServices()) {
if (gattService.getUuid().equals(MODEL_A_MEASUREMENT_SERVICE)) {
writeCharacteristic = MODEL_A_WRITE_CHARACTERISTIC;
measurementService = MODEL_A_MEASUREMENT_SERVICE;
setNotificationOn(MODEL_A_MEASUREMENT_SERVICE, MODEL_A_NOTIFICATION_CHARACTERISTIC, WEIGHT_MEASUREMENT_CONFIG);
Timber.d("Found a Model A");
break;
}
if (gattService.getUuid().equals(MODEL_B_MEASUREMENT_SERVICE)) {
writeCharacteristic = MODEL_B_WRITE_CHARACTERISTIC;
measurementService = MODEL_B_MEASUREMENT_SERVICE;
setNotificationOn(MODEL_B_MEASUREMENT_SERVICE, MODEL_B_NOTIFICATION_CHARACTERISTIC, WEIGHT_MEASUREMENT_CONFIG);
Timber.d("Found a Model B");
break;
}
}
break;
case 1:
Timber.d("Sync Date");
synchroniseDate();
break;
case 2:
Timber.d("Sync Time");
synchroniseTime();
break;
default:
return false;
}
return true;
}
@Override
protected boolean nextBluetoothCmd(int stateNr) {
return false;
}
@Override
protected boolean nextCleanUpCmd(int stateNr) {
return false;
}
@Override
public void onBluetoothDataChange(BluetoothGatt bluetoothGatt, BluetoothGattCharacteristic gattCharacteristic) {
final byte[] data = gattCharacteristic.getValue();
Timber.d("got data %d", data[0]);
if (data == null || data[0] != (byte)0xFF) {
return;
}
Timber.d("Valid data");
System.arraycopy(data, 1, data, 0, data.length - 1);
Timber.d ("got more data %d", (byte)data[0]);
switch (data[0]) {
case (byte)0xA5:
parseMeasurement(data);
break;
}
}
private void parseMeasurement(byte[] data) {
switch(data[5]) {
case (byte)0xAA:
case (byte)0xA0:
if (weightStabilized) {
return;
}
if (!stepMessageDisplayed) {
sendMessage(R.string.info_step_on_scale, 0);
stepMessageDisplayed = true;
}
weightStabilized = data[5] == (byte)0xAA;
Timber.d("the byte is %d stable is %s", (data[5] & 0xff), weightStabilized ? "true": "false");
lastWeight = ((data[1] & 0xff) << 8) | (data[2] & 0xff);
if (lastWeight > 0) {
sendMessage(R.string.info_measuring, lastWeight / 10.0f);
}
if (weightStabilized) {
values |= 1;
synchroniseUser();
}
break;
case (byte)0xBE:
setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "Fat Test Error");
disconnect(false);
break;
case (byte)0xB0:
lastFat = ((data[1] & 0xff) << 8) | (data[2] & 0xff);
lastHydration = ((data[3] & 0xff) << 8) | (data[4] & 0xff);
values |= 2;
Timber.d("got fat %d", values);
break;
case (byte)0xC0:
lastMuscle = ((data[1] & 0xff) << 8) | (data[2] & 0xff);
lastBone = ((data[4] & 0xff) << 8) | (data[3] & 0xff);
values |= 4;
Timber.d("got muscle %d", values);
break;
case (byte)0xD0:
lastKcal = ((data[1] & 0xff) << 8) | (data[2] & 0xff);
int unknown = ((data[3] & 0xff) << 8) | (data[4] & 0xff);
values |= 8;
Timber.d("got kal %d", values);
break;
}
if (values == 15) {
ScaleMeasurement scaleBtData = new ScaleMeasurement();
scaleBtData.setWeight((float)lastWeight / 10.0f);
scaleBtData.setFat((float)lastFat / 10.0f);
scaleBtData.setWater((float)lastHydration / 10.0f);
scaleBtData.setBone((float)lastBone / 10.0f);
scaleBtData.setMuscle((float)lastMuscle / 10.0f);
scaleBtData.setDateTime(new Date());
addScaleData(scaleBtData);
disconnect(false);
}
}
private void synchroniseDate() {
Calendar cal = Calendar.getInstance();
byte message[] = new byte[]{(byte)0xA5, (byte)0x30, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00};
message[2] = (byte)Integer.parseInt(Long.toHexString(Integer.valueOf(String.valueOf(cal.get(Calendar.YEAR)).substring(2))), 16);
String DayLength=Long.toHexString(cal.get(Calendar.DAY_OF_YEAR));
DayLength=DayLength.length()==1?"000"+DayLength:
DayLength.length()==2?"00"+DayLength:
DayLength.length()==3?"0"+DayLength:DayLength;
message[3]=(byte)Integer.parseInt(DayLength.substring(0,2), 16);
message[4]=(byte)Integer.parseInt(DayLength.substring(2,4), 16);
addChecksum(message);
writeBytes(measurementService, writeCharacteristic, message);
}
private void synchroniseTime() {
Calendar cal = Calendar.getInstance();
byte message[] = new byte[]{(byte)0xA5, (byte)0x31, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00};
message[2]=(byte)Integer.parseInt(Long.toHexString(cal.get(Calendar.HOUR_OF_DAY)), 16);
message[3]=(byte)Integer.parseInt(Long.toHexString(cal.get(Calendar.MINUTE)), 16);
message[4]=(byte)Integer.parseInt(Long.toHexString(cal.get(Calendar.SECOND)), 16);
addChecksum(message);
writeBytes(measurementService, writeCharacteristic, message);
}
private void addChecksum(byte[] message) {
byte verify = 0;
for(int i=1;i<message.length-2;i++){
verify=(byte) (verify+message[i] & 0xff);
}
message[message.length-2]=verify;
}
private void synchroniseUser() {
final ScaleUser selectedUser = OpenScale.getInstance().getSelectedScaleUser();
byte message[] = new byte[]{(byte)0xA5, (byte)0x10, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00};
//message[2] = (byte)((selectedUser.getGender().isMale() ? (byte)0x80: (byte)0x00) + 1+selectedUser.getId());
message[2] = (byte) ((byte)(selectedUser.getGender().isMale()?(byte)0:(byte)8)*(byte)16 + (byte)selectedUser.getId());
message[3] = (byte)selectedUser.getAge();
message[4] = (byte)selectedUser.getBodyHeight();
addChecksum(message);
writeBytes(measurementService, writeCharacteristic, message);
}
}
@oliexdev
Copy link

oliexdev commented Mar 6, 2019

could you please add the following GPLv3 notice on the top of your code, so its comply with the GPLv3?

/*   Copyright (C) 2018 <your name>
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    This program 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 General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>
*/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment