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; } }