diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 948b1d1..5ff5cdb 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,7 +2,10 @@
-
+
+
@@ -22,14 +25,14 @@
+ android:screenOrientation="portrait" />
+ android:screenOrientation="portrait" />
+
\ No newline at end of file
diff --git a/app/src/main/java/com/rubenvandeven/heartbeatstreamer/MainActivity.java b/app/src/main/java/com/rubenvandeven/heartbeatstreamer/MainActivity.java
index 7b55d09..a2cec8c 100644
--- a/app/src/main/java/com/rubenvandeven/heartbeatstreamer/MainActivity.java
+++ b/app/src/main/java/com/rubenvandeven/heartbeatstreamer/MainActivity.java
@@ -1,21 +1,130 @@
package com.rubenvandeven.heartbeatstreamer;
+import android.content.BroadcastReceiver;
+import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
+import android.util.Log;
+import android.widget.TextView;
+import android.widget.Toast;
+import com.rubenvandeven.heartbeatstreamer.heartrate.Activity_AsyncScanHeartRateSampler;
import com.rubenvandeven.heartbeatstreamer.heartrate.Activity_SearchUiHeartRateSampler;
+import com.rubenvandeven.heartbeatstreamer.heartrate.HeartRateService;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
public class MainActivity extends AppCompatActivity {
+ private final String TAG = MainActivity.class.getSimpleName();
+
+ private TextView statusLabel;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
- Intent intent = new Intent(this, Activity_SearchUiHeartRateSampler.class);
- startActivity(intent);
+ statusLabel = findViewById(R.id.status_msg);
+
+ setup();
+
+ startHeartRateMonitor();
+
}
+
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ setup();
+ }
+
+ public void setup() {
+ if (HeartRateService.isRunning()) {
+ statusLabel.setText("Running");
+ } else {
+ statusLabel.setText("Stopped");
+ }
+ registerBroadcastReceiver();
+ }
+
+ public void startHeartRateMonitor() {
+ if(HeartRateService.isRunning()) {
+ Toast.makeText(this, "Already running", Toast.LENGTH_LONG);
+ } else {
+ Intent intent = new Intent(MainActivity.this, HeartRateService.class);
+ startService(intent);
+ }
+ }
+
+ public void stopHeartRateMonitor() {
+ // stop tracking
+ Intent intent = new Intent(MainActivity.this, HeartRateService.class);
+ stopService(intent);
+ }
+
+ /**
+ * Register broadcast receiver for synchronization
+ * and tracking status updates
+ */
+ private void registerBroadcastReceiver() {
+ Log.d(TAG, "register broadcastreceiver");
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(HeartRateService.BROADCAST_MONITOR_CONNECTED);
+ filter.addAction(HeartRateService.BROADCAST_MONITOR_DISCONNECTED);
+ filter.addAction(HeartRateService.BROADCAST_MONITOR_UPDATE);
+ filter.addAction(HeartRateService.BROADCAST_MONITOR_UNKNOWN);
+ filter.addAction(HeartRateService.BROADCAST_SOCKET_SENT);
+ registerReceiver(mBroadcastReceiver, filter);
+ }
+
+ /**
+ * On pause
+ */
+ @Override
+ protected void onPause() {
+ unregisterReceiver(mBroadcastReceiver);
+// if (db != null) {
+// db.close();
+// }
+ super.onPause();
+ }
+
+ /**
+ * Broadcast receiver
+ */
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(TAG, "[broadcast received " + intent + "]");
+ if (intent == null || intent.getAction() == null) {
+ return;
+ }
+ switch (intent.getAction()) {
+ case HeartRateService.BROADCAST_MONITOR_CONNECTED:
+ statusLabel.setText("Running");
+ break;
+ case HeartRateService.BROADCAST_MONITOR_DISCONNECTED:
+ statusLabel.setText("Stopped");
+ break;
+ case HeartRateService.BROADCAST_MONITOR_UPDATE:
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss");
+ String currentDateandTime = sdf.format(new Date());
+ statusLabel.setText("Last update: %s".format(currentDateandTime));
+ break;
+
+ case HeartRateService.BROADCAST_SOCKET_SENT:
+ String msg = intent.getStringExtra("msg");
+ statusLabel.setText("Last msg: %s".format(msg));
+ break;
+ }
+ }
+ };
+
}
diff --git a/app/src/main/java/com/rubenvandeven/heartbeatstreamer/heartrate/Activity_AsyncScanHeartRateSampler.java b/app/src/main/java/com/rubenvandeven/heartbeatstreamer/heartrate/Activity_AsyncScanHeartRateSampler.java
index b466d1c..ef9f8f2 100644
--- a/app/src/main/java/com/rubenvandeven/heartbeatstreamer/heartrate/Activity_AsyncScanHeartRateSampler.java
+++ b/app/src/main/java/com/rubenvandeven/heartbeatstreamer/heartrate/Activity_AsyncScanHeartRateSampler.java
@@ -10,6 +10,7 @@ All rights reserved.
package com.rubenvandeven.heartbeatstreamer.heartrate;
import android.os.Bundle;
+import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
@@ -98,6 +99,7 @@ public class Activity_AsyncScanHeartRateSampler extends Activity_HeartRateDispla
*/
protected void requestConnectToResult(final AsyncScanResultDeviceInfo asyncScanResultDeviceInfo)
{
+ Log.i("HeartBeat", "Now go!" + asyncScanResultDeviceInfo.getDeviceDisplayName() + " " + asyncScanResultDeviceInfo.getAntDeviceNumber());
//Inform the user we are connecting
runOnUiThread(new Runnable()
{
@@ -170,6 +172,7 @@ public class Activity_AsyncScanHeartRateSampler extends Activity_HeartRateDispla
//since the user most likely wants to be aware of which device they are already using in another app
if(deviceFound.isAlreadyConnected())
{
+
mAlreadyConnectedDeviceInfos.add(deviceFound);
runOnUiThread(new Runnable()
{
@@ -188,16 +191,23 @@ public class Activity_AsyncScanHeartRateSampler extends Activity_HeartRateDispla
}
else
{
- mScannedDeviceInfos.add(deviceFound);
- runOnUiThread(new Runnable()
- {
- @Override
- public void run()
+ Log.i("HeartBeat", "Found device :-) " + deviceFound.getDeviceDisplayName());
+ if(deviceFound.getDeviceDisplayName().contentEquals("Rubensheart")) {
+ Log.i("HeartBeat", "Attempt connect!");
+ // No check or anything after first connect. Good enough for this single use implementation.
+ requestConnectToResult(deviceFound);
+ } else{
+ mScannedDeviceInfos.add(deviceFound);
+ runOnUiThread(new Runnable()
{
- adapter_devNameList.add(deviceFound.getDeviceDisplayName());
- adapter_devNameList.notifyDataSetChanged();
- }
- });
+ @Override
+ public void run()
+ {
+ adapter_devNameList.add(deviceFound.getDeviceDisplayName());
+ adapter_devNameList.notifyDataSetChanged();
+ }
+ });
+ }
}
}
});
diff --git a/app/src/main/java/com/rubenvandeven/heartbeatstreamer/heartrate/Activity_HeartRateDisplayBase.java b/app/src/main/java/com/rubenvandeven/heartbeatstreamer/heartrate/Activity_HeartRateDisplayBase.java
index a036df3..54ec9f1 100644
--- a/app/src/main/java/com/rubenvandeven/heartbeatstreamer/heartrate/Activity_HeartRateDisplayBase.java
+++ b/app/src/main/java/com/rubenvandeven/heartbeatstreamer/heartrate/Activity_HeartRateDisplayBase.java
@@ -185,19 +185,21 @@ public abstract class Activity_HeartRateDisplayBase extends AppCompatActivity
public void onCompleted(Exception ex, final WebSocket webSocket) {
if (ex != null) {
ex.printStackTrace();
- // TODO: retry
+ // TODO: retry in 30s - ie. after internet loss
return;
}
webSocket.send("Connect!");
- webSocket.setClosedCallback(new CompletedCallback() {
+ CompletedCallback reconnectCallback = new CompletedCallback() {
@Override
public void onCompleted(Exception ex) {
//unsubscribe before triggering resubscription
hrPcc.subscribeHeartRateDataEvent(null);
subscribeToHrEvents();
}
- });
+ };
+ webSocket.setClosedCallback(reconnectCallback);
+ webSocket.setEndCallback(reconnectCallback);
hrPcc.subscribeHeartRateDataEvent(new IHeartRateDataReceiver()
{
diff --git a/app/src/main/java/com/rubenvandeven/heartbeatstreamer/heartrate/HeartRateService.java b/app/src/main/java/com/rubenvandeven/heartbeatstreamer/heartrate/HeartRateService.java
new file mode 100644
index 0000000..b33c53e
--- /dev/null
+++ b/app/src/main/java/com/rubenvandeven/heartbeatstreamer/heartrate/HeartRateService.java
@@ -0,0 +1,227 @@
+package com.rubenvandeven.heartbeatstreamer.heartrate;
+
+import android.app.AlertDialog;
+import android.app.Service;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.util.Log;
+import android.view.View;
+import android.widget.Toast;
+
+import com.dsi.ant.plugins.antplus.pcc.AntPlusHeartRatePcc;
+import com.dsi.ant.plugins.antplus.pcc.defines.DeviceState;
+import com.dsi.ant.plugins.antplus.pcc.defines.EventFlag;
+import com.dsi.ant.plugins.antplus.pcc.defines.RequestAccessResult;
+import com.dsi.ant.plugins.antplus.pccbase.AntPluginPcc;
+import com.dsi.ant.plugins.antplus.pccbase.AsyncScanController;
+import com.dsi.ant.plugins.antplus.pccbase.PccReleaseHandle;
+import com.koushikdutta.async.future.Future;
+import com.koushikdutta.async.http.AsyncHttpClient;
+import com.koushikdutta.async.http.WebSocket;
+import com.rubenvandeven.heartbeatstreamer.R;
+
+import java.math.BigDecimal;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.EnumSet;
+
+public class HeartRateService extends Service {
+ private static final String TAG = HeartRateService.class.getSimpleName();
+
+ public static final String BROADCAST_MONITOR_CONNECTED = "com.rubenvandeven.heartbeat.broadcast.monitor_started";
+ public static final String BROADCAST_MONITOR_DISCONNECTED = "com.rubenvandeven.heartbeat.broadcast.monitor_stopped";
+ public static final String BROADCAST_MONITOR_UNKNOWN = "com.rubenvandeven.heartbeat.broadcast.monitor_unknown";
+ public static final String BROADCAST_MONITOR_UPDATE = "com.rubenvandeven.heartbeat.broadcast.monitor_update";
+ public static final String BROADCAST_SOCKET_SENT = "com.rubenvandeven.heartbeat.broadcast.socket_sent";
+
+ AsyncScanController hrScanCtrl;
+
+ AntPlusHeartRatePcc hrPcc;
+ PccReleaseHandle releaseHandle;
+ String lastMsg = "";
+
+ public static boolean isRunning = false;
+
+ @Override
+ public void onCreate() {
+ isRunning = true;
+ requestAccessToPcc();
+ }
+
+
+ /**
+ * Start main thread, request location updates, start synchronization.
+ *
+ * @param intent Intent
+ * @param flags Flags
+ * @param startId Unique id
+ * @return Always returns START_STICKY
+ */
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Log.i(TAG, "Received start id " + startId + ": " + intent);
+ return START_STICKY;
+ }
+
+ protected AntPluginPcc.IDeviceStateChangeReceiver stateChangeReceiver =
+ new AntPluginPcc.IDeviceStateChangeReceiver()
+ {
+ @Override
+ public void onDeviceStateChange(final DeviceState state)
+ {
+ if(DeviceState.DEAD.equals(state) || DeviceState.CLOSED.equals(state)) {
+ sendBroadcast(new Intent(BROADCAST_MONITOR_DISCONNECTED));
+ waitAndReconnect();
+ } else if (DeviceState.TRACKING.equals(state)) {
+ sendBroadcast(new Intent(BROADCAST_MONITOR_CONNECTED));
+ } else {
+ Intent i = new Intent(BROADCAST_MONITOR_UNKNOWN);
+ i.putExtra("status", state.getIntValue());
+ sendBroadcast(i);
+ }
+ }
+ };
+
+ protected AntPluginPcc.IPluginAccessResultReceiver resultReceiver =
+ new AntPluginPcc.IPluginAccessResultReceiver() {
+ //Handle the result, connecting to events on success or reporting failure to user.
+ @Override
+ public void onResultReceived(AntPlusHeartRatePcc result, RequestAccessResult resultCode,
+ DeviceState initialDeviceState) {
+// showDataDisplay("Connecting...");
+ switch (resultCode) {
+ case SUCCESS:
+ connectHr(result);
+ break;
+ case CHANNEL_NOT_AVAILABLE:
+ showNotification("Channel Not Available");
+ break;
+ case OTHER_FAILURE:
+ showNotification("RequestAccess failed. See logcat for details.");
+ break;
+ case USER_CANCELLED:
+ showNotification("Cancelled. Do reset.");
+ break;
+ case UNRECOGNIZED:
+ default:
+ showNotification("Unknown error. Do reset.");
+ break;
+ }
+ }
+ };
+
+ private void connectHr(AntPlusHeartRatePcc result) {
+ hrPcc = result; // keep track of current connection
+ hrPcc.subscribeHeartRateDataEvent(new AntPlusHeartRatePcc.IHeartRateDataReceiver()
+ {
+ @Override
+ public void onNewHeartRateData(final long estTimestamp, EnumSet eventFlags,
+ final int computedHeartRate, final long heartBeatCount,
+ final BigDecimal heartBeatEventTime, final AntPlusHeartRatePcc.DataState dataState)
+ {
+ DecimalFormatSymbols otherSymbols = new DecimalFormatSymbols();
+ otherSymbols.setDecimalSeparator('.');
+ DecimalFormat df = new DecimalFormat("#.##", otherSymbols);
+
+ // Mark heart rate with asterisk if zero detected
+ final String textHeartRate = String.valueOf(computedHeartRate)
+ + ((AntPlusHeartRatePcc.DataState.ZERO_DETECTED.equals(dataState)) ? "*" : "");
+
+
+ // Mark heart beat count and heart beat event time with asterisk if initial value
+ final String textHeartBeatCount = String.valueOf(heartBeatCount)
+ + ((AntPlusHeartRatePcc.DataState.INITIAL_VALUE.equals(dataState)) ? "*" : "");
+ final String textHeartBeatEventTime = df.format(heartBeatEventTime)
+ + ((AntPlusHeartRatePcc.DataState.INITIAL_VALUE.equals(dataState)) ? "*" : "");
+
+ final String msg = String.format("{\"rate\":\"%s\", \"count\":\"%s\", \"time\":\"%s\"}", textHeartRate, textHeartBeatCount, textHeartBeatEventTime);
+
+ if(msg.contentEquals(lastMsg)) {
+ Log.i(TAG, "Skip duplicate");
+ return;
+ }
+ lastMsg = msg;
+
+ sendResult(msg);
+ }
+ });
+
+ }
+
+
+ private void sendResult(String msg) {
+ Log.d(TAG, "Send: " + msg);
+ final String m = msg;
+ Future fws = AsyncHttpClient.getDefaultInstance().websocket("ws://heartbeat.rubenvandeven.com:8888/ws", "my-protocol", new AsyncHttpClient.WebSocketConnectCallback() {
+ @Override
+ public void onCompleted(Exception ex, final WebSocket webSocket) {
+ if (ex != null) {
+ ex.printStackTrace();
+ // TODO: retry in 30s - ie. after internet loss
+ return;
+ }
+ webSocket.send(m);
+ Intent intent = new Intent(BROADCAST_SOCKET_SENT);
+ intent.putExtra("msg", m);
+ sendBroadcast(intent);
+ webSocket.close();
+ }
+ });
+ }
+
+ private void showNotification(String msg) {
+ final String m = msg;
+ Handler handler = new Handler(Looper.getMainLooper());
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(HeartRateService.this.getApplicationContext(),m,Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ /**
+ * wait to reconnect to dead HR-monitor
+ */
+ protected void waitAndReconnect() {
+ //TODO:
+ Log.d(TAG, "Wait & reconnect");
+ requestAccessToPcc();
+ }
+
+ /**
+ * Requests the ANT+ device
+ */
+ protected void requestAccessToPcc()
+ {
+ if(hrPcc != null) {
+ hrPcc.subscribeHeartRateDataEvent(null);
+ }
+ releaseHandle = AntPlusHeartRatePcc.requestAccess(this, 4818, 0, resultReceiver, stateChangeReceiver);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ // TODO: Return the communication channel to the service.
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+
+ @Override
+ public void onDestroy() {
+ if(releaseHandle != null)
+ {
+ releaseHandle.close();
+ }
+ isRunning = false;
+ super.onDestroy();
+ }
+
+ public static boolean isRunning() {
+ return isRunning;
+ }
+}
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 84f1951..98182a1 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -9,7 +9,8 @@