/* Copyright (C) 2014 olie.xdev <[email protected]> * Copyright (C) 2018 John Lines <[email protected]> * * 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/> */ package com.health.openscale.core.bluetooth; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothSocket; import android.content.Context; import com.health.openscale.core.datatypes.ScaleMeasurement; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Arrays; import java.util.Date; import java.util.UUID; import timber.log.Timber; public class BluetoothIhealthHS3 extends BluetoothCommunication { private final UUID uuid = BluetoothGattUuid.fromShortCode(0x1101); // Standard SerialPortService ID private BluetoothSocket btSocket = null; private BluetoothDevice btDevice = null; private BluetoothConnectedThread btConnectThread = null; private byte[] lastWeight = new byte[2]; private Date lastWeighed = new Date(); private final long maxTimeDiff = 60000; // maximum time interval we will consider two identical // weight readings to be the same and hence ignored - 60 seconds in milliseconds public BluetoothIhealthHS3(Context context) { super(context); } @Override public String driverName() { return "iHealth HS33FA4A"; } @Override protected boolean onNextStep(int stepNr) { Timber.w("ihealthHS3 - onNextStep - returning false"); return false; } @Override public void connect(String hwAddress) { BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter(); if (btAdapter == null) { setBluetoothStatus(BT_STATUS.NO_DEVICE_FOUND); return; } btDevice = btAdapter.getRemoteDevice(hwAddress); try { // Get a BluetoothSocket to connect with the given BluetoothDevice btSocket = btDevice.createRfcommSocketToServiceRecord(uuid); } catch (IOException e) { setBluetoothStatus(BT_STATUS.UNEXPECTED_ERROR, "Can't get a bluetooth socket"); btDevice = null; return; } Thread socketThread = new Thread() { @Override public void run() { try { if (!btSocket.isConnected()) { // Connect the device through the socket. This will block // until it succeeds or throws an exception btSocket.connect(); // Bluetooth connection was successful setBluetoothStatus(BT_STATUS.CONNECTION_ESTABLISHED); btConnectThread = new BluetoothConnectedThread(); btConnectThread.start(); } } catch (IOException connectException) { // Unable to connect; close the socket and get out disconnect(); setBluetoothStatus(BT_STATUS.NO_DEVICE_FOUND); } } }; socketThread.start(); } @Override public void disconnect() { Timber.w("HS3 - disconnect"); if (btSocket != null) { if (btSocket.isConnected()) { try { btSocket.close(); btSocket = null; } catch (IOException closeException) { setBluetoothStatus(BT_STATUS.UNEXPECTED_ERROR, "Can't close bluetooth socket"); } } } if (btConnectThread != null) { btConnectThread.cancel(); btConnectThread = null; } btDevice = null; } private boolean sendBtData(String data) { Timber.w("ihealthHS3 - sendBtData %s", data); if (btSocket.isConnected()) { btConnectThread = new BluetoothConnectedThread(); btConnectThread.write(data.getBytes()); btConnectThread.cancel(); return true; } Timber.w("ihealthHS3 - sendBtData - socket is not connected"); return false; } private class BluetoothConnectedThread extends Thread { private InputStream btInStream; private OutputStream btOutStream; private volatile boolean isCancel; public BluetoothConnectedThread() { // Timber.w("ihealthHS3 - BluetoothConnectedThread"); isCancel = false; // Get the input and output bluetooth streams try { btInStream = btSocket.getInputStream(); btOutStream = btSocket.getOutputStream(); } catch (IOException e) { setBluetoothStatus(BT_STATUS.UNEXPECTED_ERROR, "Can't get bluetooth input or output stream " + e.getMessage()); } } public void run() { byte btByte; byte[] weightBytes = new byte[2]; // Timber.w("ihealthHS3 - run"); // Keep listening to the InputStream until an exception occurs (e.g. device partner goes offline) while (!isCancel) { try { // stream read is a blocking method btByte = (byte) btInStream.read(); // Timber.w("iheathHS3 - seen a byte "+String.format("%02X",btByte)); if ( btByte == (byte) 0xA0 ) { btByte = (byte) btInStream.read(); if ( btByte == (byte) 0x09 ) { btByte = (byte) btInStream.read(); if ( btByte == (byte) 0xa6 ) { btByte = (byte) btInStream.read(); if ( btByte == (byte) 0x28 ) { // Timber.w("seen 0xa009a628 - Weight packet"); // deal with a weight packet - read 5 bytes we dont care about btByte = (byte) btInStream.read(); btByte = (byte) btInStream.read(); btByte = (byte) btInStream.read(); btByte = (byte) btInStream.read(); btByte = (byte) btInStream.read(); // and the weight - which should follow weightBytes[0] = (byte) btInStream.read(); weightBytes[1] = (byte) btInStream.read(); ScaleMeasurement scaleMeasurement = parseWeightArray(weightBytes); if (scaleMeasurement != null) { addScaleMeasurement(scaleMeasurement); } } else if (btByte == (byte) 0x33 ) { Timber.w("seen 0xa009a633 - time packet"); // deal with a time packet, if needed } else { Timber.w("iHealthHS3 - seen byte after control leader %02X", btByte); } } } } } catch (IOException e) { cancel(); setBluetoothStatus(BT_STATUS.CONNECTION_LOST); } } } private ScaleMeasurement parseWeightArray(byte[] weightBytes ) throws IOException { ScaleMeasurement scaleBtData = new ScaleMeasurement(); // Timber.w("iHealthHS3 - ScaleMeasurement "+String.format("%02X",weightBytes[0])+String.format("%02X",weightBytes[1])); String ws = String.format("%02X",weightBytes[0])+String.format("%02X",weightBytes[1]); StringBuilder ws1 = new StringBuilder (ws); ws1.insert(ws.length()-1,"."); float weight = Float.parseFloat(ws1.toString()); // Timber.w("iHealthHS3 - ScaleMeasurement "+String.format("%f",weight)); Date now = new Date(); // If the weight is the same as the lastWeight, and the time since the last reading is less than maxTimeDiff then return null if (Arrays.equals(weightBytes,lastWeight) && (now.getTime() - lastWeighed.getTime() < maxTimeDiff)) { // Timber.w("iHealthHS3 - parseWeightArray returning null"); return null; } scaleBtData.setDateTime(now); scaleBtData.setWeight(weight); lastWeighed = now; System.arraycopy(weightBytes,0,lastWeight,0,lastWeight.length); return scaleBtData; } public void write(byte[] bytes) { try { btOutStream.write(bytes); } catch (IOException e) { setBluetoothStatus(BT_STATUS.UNEXPECTED_ERROR, "Error while writing to bluetooth socket " + e.getMessage()); } } public void cancel() { isCancel = true; } } }