diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothCommunication.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothCommunication.java index 34271575..ce08f44c 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothCommunication.java +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothCommunication.java @@ -44,7 +44,7 @@ public abstract class BluetoothCommunication { public enum BT_MACHINE_STATE {BT_INIT_STATE, BT_CMD_STATE, BT_CLEANUP_STATE} public enum BT_DEVICE_ID {CUSTOM_OPENSCALE, MI_SCALE_V1, MI_SCALE_V2, SANITAS_SBF70, MEDISANA_BS444, DIGOO_DGS038H, EXCELVANT_CF369BLE, - YUNMAI_MINI, YUNMAI_SE, MGB, EXINGTECH_Y1, BEURER_BF700_800, HESLEY, ONEBYONE} + YUNMAI_MINI, YUNMAI_SE, MGB, EXINGTECH_Y1, BEURER_BF700_800, HESLEY, ONEBYONE, IHEALTH_HS3} protected Context context; @@ -112,6 +112,8 @@ public abstract class BluetoothCommunication { return new BluetoothHesley(context); case ONEBYONE: return new BluetoothOneByone(context); + case IHEALTH_HS3: + return new BluetoothIhealthHS3(context); } return null; diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothIhealthHS3.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothIhealthHS3.java new file mode 100644 index 00000000..b71417b3 --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothIhealthHS3.java @@ -0,0 +1,314 @@ +/* Copyright (C) 2014 olie.xdev +* Copyright (C) 2018 John Lines +* +* 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 +*/ + +package com.health.openscale.core.bluetooth; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothSocket; +import android.content.Context; +import android.util.Log; + +import com.health.openscale.core.datatypes.ScaleMeasurement; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Set; +import java.util.UUID; +import java.util.Date; +import java.util.Calendar; +import java.util.Arrays; + +public class BluetoothIhealthHS3 extends BluetoothCommunication { + private final UUID uuid = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb"); // 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 deviceName() { + Log.w("openscale","ihealthHS3 - DeviceName - returning iHealth HS33FA4A"); + return "iHealth HS33FA4A"; + } + + @Override + public String defaultDeviceName() { + Log.w("openscale","ihealthHS3 - defaultDeviceName - returning iHealth HS3"); + return "iHealth HS3"; + } + + @Override + public boolean checkDeviceName(String btDeviceName) { + Log.w("openscale","ihealthHS3 - checkDeviceName "+btDeviceName); + if (btDeviceName.startsWith("iHealth HS3")) { + return true; + } + + return false; + } + + + @Override + boolean nextInitCmd(int stateNr) { + Log.w("openscale","ihealthHS3 - nextInitCmd - returning false"); + return false; + } + + @Override + boolean nextBluetoothCmd(int stateNr) { + Log.w("openscale","ihealthHS3 - nextBluetoothCmd - returning false"); + return false; + } + + @Override + boolean nextCleanUpCmd(int stateNr) { + Log.w("openscale","ihealthHS3 - nextCleanUpCmd - returning false"); + return false; + } + + public boolean isBLE() { + Log.w("openscale","iHealth HS3 - isBLE - returning false"); + return false; + } + + + @Override + public void startSearching(String deviceName) { + +// Log.w("openscale","iHealth HS3 - startSearching "+deviceName); + + if (btAdapter == null) { + setBtStatus(BT_STATUS_CODE.BT_NO_DEVICE_FOUND); + return; + } + + Set pairedDevices = btAdapter.getBondedDevices(); + +// Log.w("openscale","about to start searching paired devices"); + + for (BluetoothDevice device : pairedDevices) { + // check if we can found bluetooth device name in the pairing list +// if (device != null ) { Log.w("openscale","Looking at device "+device.getName()); } ; + + if (device != null && device.getName().equals(deviceName)) { + btDevice = device; + + try { + // Get a BluetoothSocket to connect with the given BluetoothDevice + btSocket = btDevice.createRfcommSocketToServiceRecord(uuid); + } catch (IOException e) { + setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "Can't get a bluetooth socket"); + } + + 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 + setBtStatus(BT_STATUS_CODE.BT_CONNECTION_ESTABLISHED); + + btConnectThread = new BluetoothConnectedThread(); + btConnectThread.start(); + } + } catch (IOException connectException) { + // Unable to connect; close the socket and get out + stopSearching(); + setBtStatus(BT_STATUS_CODE.BT_NO_DEVICE_FOUND); + } + } + }; + + socketThread.start(); + return; + } + } + + setBtStatus(BT_STATUS_CODE.BT_NO_DEVICE_FOUND); + } + + @Override + public void stopSearching() { + + Log.w("openscale","HS3 - stopSearching"); + if (btSocket != null) { + if (btSocket.isConnected()) { + try { + btSocket.close(); + btSocket = null; + } catch (IOException closeException) { + setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "Can't close bluetooth socket"); + } + } + } + + if (btConnectThread != null) { + btConnectThread.cancel(); + btConnectThread = null; + } + } + + + private boolean sendBtData(String data) { + Log.w("openscale","ihealthHS3 - sendBtData"+data); + if (btSocket.isConnected()) { + btConnectThread = new BluetoothConnectedThread(); + btConnectThread.write(data.getBytes()); + + btConnectThread.cancel(); + + return true; + } + Log.w("openscale","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() { +// Log.w("openscale","ihealthHS3 - BluetoothConnectedThread"); + isCancel = false; + + // Get the input and output bluetooth streams + try { + btInStream = btSocket.getInputStream(); + btOutStream = btSocket.getOutputStream(); + } catch (IOException e) { + setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "Can't get bluetooth input or output stream " + e.getMessage()); + } + } + + public void run() { + + byte btByte; + byte[] weightBytes = new byte[2]; +// Log.w("openscale","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(); +// Log.w("openscale","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 ) { +// Log.w("openscale","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) { + addScaleData(scaleMeasurement); + } + + } + else if (btByte == (byte) 0x33 ) { + Log.w("openscale","seen 0xa009a633 - time packet"); + // deal with a time packet, if needed + } else { + Log.w("openscale","iHealthHS3 - seen byte after control leader "+String.format("%02X",btByte)); + } + } + } + } + + + + } catch (IOException e) { + cancel(); + setBtStatus(BT_STATUS_CODE.BT_CONNECTION_LOST); + } + } + } + + private ScaleMeasurement parseWeightArray(byte[] weightBytes ) throws IOException { + ScaleMeasurement scaleBtData = new ScaleMeasurement(); + +// Log.w("openscale","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()); +// Log.w("openscale","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)) { +// Log.w("openscale","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) { + setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "Error while writing to bluetooth socket " + e.getMessage()); + } + } + + public void cancel() { + isCancel = true; + } + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/gui/preferences/BluetoothPreferences.java b/android_app/app/src/main/java/com/health/openscale/gui/preferences/BluetoothPreferences.java index 25f60a7b..008c234f 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/preferences/BluetoothPreferences.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/preferences/BluetoothPreferences.java @@ -46,6 +46,7 @@ import java.util.List; import java.util.Map; import java.util.Set; + public class BluetoothPreferences extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener { private static final String PREFERENCE_KEY_BLUETOOTH_SMARTUSERASSIGN = "smartUserAssign"; private static final String PREFERENCE_KEY_BLUETOOTH_IGNOREOUTOFRANGE = "ignoreOutOfRange"; @@ -136,6 +137,7 @@ public class BluetoothPreferences extends PreferenceFragment implements SharedPr btAdapter = BluetoothAdapter.getDefaultAdapter(); + addPreferencesFromResource(R.xml.bluetooth_preferences); smartAssignEnable = (CheckBoxPreference) findPreference(PREFERENCE_KEY_BLUETOOTH_SMARTUSERASSIGN);