close #26 - outputstream implementiert, welche kontinuirliche daten schreibt

close #24 - können jetzt ohne probleme kommentare schreiben
close #22 - startet beim frischen start aber auch beim onResume die phone app
close #18 - knallt nicht mehr wenn bei 0 gestartet wird.
close #9 - verbinden sich wunderbar, nachrichten fliegen und daten werden übertragen
close #8 - sensordaten werden in ein .csv files geschrieben inkl kommentare
This commit is contained in:
root
2018-01-16 18:24:22 +01:00
parent b00c845c5c
commit 4dd9dde6fb
27 changed files with 1101 additions and 306 deletions

View File

@@ -8,8 +8,9 @@ android {
applicationId "de.tonifetzer.conductorswatch"
minSdkVersion 23
targetSdkVersion 26
versionCode 1
versionName "1.0"
//sdk 2 | product version 3 | build num 2 | multi-apk 2
versionCode 260120101
versionName "0.1.2"
}
buildTypes {
release {

View File

@@ -231,6 +231,7 @@ public class Croller extends View {
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//called way to much!
if (mProgressChangeListener != null)
mProgressChangeListener.onProgressChanged((int) (deg - 2));
@@ -502,8 +503,10 @@ public class Croller extends View {
}
public void setProgress(int x) {
deg = x + 2;
invalidate();
if(deg != x + 2){
deg = x + 2;
invalidate();
}
}
public String getLabel() {
@@ -511,8 +514,10 @@ public class Croller extends View {
}
public void setLabel(String txt) {
label = txt;
invalidate();
if(!label.equals(txt)){
label = txt;
invalidate();
}
}
public int getBackCircleColor() {
@@ -520,8 +525,10 @@ public class Croller extends View {
}
public void setBackCircleColor(int backCircleColor) {
this.backCircleColor = backCircleColor;
invalidate();
if(this.backCircleColor != backCircleColor){
this.backCircleColor = backCircleColor;
invalidate();
}
}
public int getMainCircleColor() {
@@ -529,8 +536,10 @@ public class Croller extends View {
}
public void setMainCircleColor(int mainCircleColor) {
this.mainCircleColor = mainCircleColor;
invalidate();
if(this.mainCircleColor != mainCircleColor){
this.mainCircleColor = mainCircleColor;
invalidate();
}
}
private ValueAnimator mainCircleAnimation;

View File

@@ -6,50 +6,40 @@ import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Handler;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import de.tonifetzer.conductorswatch.bpmEstimation.AccelerometerData;
import de.tonifetzer.conductorswatch.bpmEstimation.AccelerometerWindowBuffer;
import de.tonifetzer.conductorswatch.bpmEstimation.BpmEstimator;
import de.tonifetzer.conductorswatch.utilities.Utils;
import de.tonifetzer.conductorswatch.network.SensorDataFileSender;
import de.tonifetzer.conductorswatch.network.SensorDataFileStreamer;
import de.tonifetzer.conductorswatch.utilities.ByteStreamWriter;
/**
* Created by toni on 13/11/17.
*/
//TODO: einfügen der logik autoCorr + FindPeaks
public class Estimator implements SensorEventListener {
private SensorManager mSensorManager;
private Sensor mAccelerometer;
private Sensor mGyroscope;
private Context mContext;
private AccelerometerWindowBuffer mAccelerometerWindowBuffer;
private BpmEstimator mBpmEstimator;
private Handler mHandler;
private int mUpdaterate_ms = 100;
private boolean mSensorUpdateFlag = false;
private ByteStreamWriter mByteStreamWriterAcc;
private ByteStreamWriter mByteStreamWriterGyro;
private SensorDataFileStreamer mStreamer;
private final Runnable mProcessSensors = new Runnable() {
@Override
public void run() {
if(mAccelerometerWindowBuffer.isNextWindowReady()){
double bpm = mBpmEstimator.estimate();
for (OnBpmEstimatorListener listener:listeners) {
listener.onNewDataAvailable(bpm);
}
}
mSensorUpdateFlag = true;
// The Runnable is posted to run again here:
mHandler.postDelayed(this, mUpdaterate_ms);
}
};
private Timer mTimer = new Timer();
public Estimator(Context mContext){
@@ -60,36 +50,73 @@ public class Estimator implements SensorEventListener {
mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION);
mGyroscope = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_FASTEST);
mSensorManager.registerListener(this, mGyroscope, SensorManager.SENSOR_DELAY_FASTEST);
mAccelerometerWindowBuffer = new AccelerometerWindowBuffer(1024, 256);
mBpmEstimator = new BpmEstimator(mAccelerometerWindowBuffer, 0, 5000);
mHandler = new Handler();
mHandler.post(mProcessSensors); //start runnable
mTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
if (mAccelerometerWindowBuffer.isNextWindowReady()) {
double bpm = mBpmEstimator.estimate(mAccelerometerWindowBuffer.getFixedWindow());
for (OnBpmEstimatorListener listener : listeners) {
listener.onNewDataAvailable(bpm);
}
}
}
}, 0, 100);
mByteStreamWriterAcc = new ByteStreamWriter();
mByteStreamWriterGyro = new ByteStreamWriter();
mStreamer = new SensorDataFileStreamer(mContext);
new Thread(mStreamer).start();
}
public void stop() {
mHandler.removeCallbacks(mProcessSensors); //remove runnable
mTimer.cancel();
mTimer.purge();
mSensorManager.unregisterListener(this);
//send data and close the ByteStreamWriter
for (OnBpmEstimatorListener listener : listeners) {
//listener.onEstimationStopped(mByteStreamWriter.getByteArray());
}
mByteStreamWriterAcc.close();
mByteStreamWriterGyro.close();
mStreamer.close();
}
@Override
public void onSensorChanged(SensorEvent se) {
// einkommentieren, falls die updaterate beschränkt werden soll. aktuell maximum speed
//if(mSensorUpdateFlag) {
//TODO: at the moment this runs in main thread... since worker fragment runs also in main thread
//ca 200hz, every 5 to 6 ms we have an update
if (se.sensor.getType() == Sensor.TYPE_LINEAR_ACCELERATION) {
mAccelerometerWindowBuffer.add(new AccelerometerData(System.currentTimeMillis(), se.values[0], se.values[1], se.values[2]));
mByteStreamWriterAcc.writeSensor3D(Sensor.TYPE_LINEAR_ACCELERATION, se.values[0], se.values[1], se.values[2]);
mStreamer.sendByteArray(mByteStreamWriterAcc.getByteArray());
mByteStreamWriterAcc.reset();
}
//TODO: get also gyro and write stuff into file
if (se.sensor.getType() == Sensor.TYPE_GYROSCOPE) {
//TODO: Rename AccelerometerData to SensorData3D
mByteStreamWriterGyro.writeSensor3D(Sensor.TYPE_GYROSCOPE, se.values[0], se.values[1], se.values[2]);
// mSensorUpdateFlag = false;
//}
mStreamer.sendByteArray(mByteStreamWriterGyro.getByteArray());
mByteStreamWriterGyro.reset();
}
}
@Override
@@ -97,12 +124,12 @@ public class Estimator implements SensorEventListener {
// do nothin
}
/**
* Interface for callback calculated bpm
*/
public interface OnBpmEstimatorListener {
void onNewDataAvailable(double bpm);
void onEstimationStopped(byte[] sensorData);
}
private List<OnBpmEstimatorListener> listeners = new CopyOnWriteArrayList<OnBpmEstimatorListener>();

View File

@@ -7,48 +7,29 @@ import android.graphics.Point;
import android.os.Bundle;
import android.os.Handler;
import android.os.Vibrator;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.wearable.activity.WearableActivity;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.widget.TextView;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.wearable.MessageApi;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.Wearable;
import java.sql.Time;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.TimeUnit;
import de.tonifetzer.conductorswatch.network.SensorDataFileSender;
import de.tonifetzer.conductorswatch.utilities.Utils;
public class MainActivity extends WearableActivity implements WorkerFragment.OnFragmentInteractionListener, TapBpm.OnTapBpmListener, GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener{
public class MainActivity extends WearableActivity implements WorkerFragment.OnFragmentInteractionListener, TapBpm.OnTapBpmListener {
// member
private TextView mTextView;
private Croller mCroller;
private GestureDetector mDetector;
private boolean mModeRecord;
private boolean mModeRecord = false;
// connection to phone stuff
private static final String START_ACTIVITY_PATH = "/start-activity";
private static final String START_RECORD_PATH = "/start-record";
private static final String STOP_RECORD_PATH = "/stop-record";
private static final String UPDATE_PATH = "/update";
public static final String TAG = "DataLayerListenerService";
private GoogleApiClient mGoogleApiClient;
private Node mNode;
private boolean mResolvingError = false;
private SensorDataFileSender mSender;
private volatile boolean mReadyToSend = true;
// display center
private int mDisplayWidth;
@@ -56,7 +37,8 @@ public class MainActivity extends WearableActivity implements WorkerFragment.OnF
private Point mDisplayCenter;
// saved Bpm to reset after recording
private int mMetronomBpm;
private int mMetronomBpm = 80;
private int mLastSendBPM = 80;
// tapping
private Thread mTapBpmThread;
@@ -81,7 +63,7 @@ public class MainActivity extends WearableActivity implements WorkerFragment.OnF
mModeRecord = !mModeRecord;
if(mModeRecord){
if (mModeRecord) {
WorkerFragment worker = new WorkerFragment();
//provide the fragment with the bpm set
@@ -90,71 +72,71 @@ public class MainActivity extends WearableActivity implements WorkerFragment.OnF
worker.setArguments(args);
//send data to phone, that we start to record
String curProgress = Integer.toString(mCroller.getProgress());
sendMessage(START_RECORD_PATH + ":" + curProgress + ":" + curProgress);
mMetronomBpm = mCroller.getProgress();
mSender.sendMessage(START_RECORD_PATH + ":" + mMetronomBpm + ":" + mMetronomBpm);
//setter
worker.setCrollerWorker(mCroller);
worker.setTextViewWorker(mTextView);
mMetronomBpm = mCroller.getProgress();
//create fragment instance
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.layout, worker, "Worker");
transaction.addToBackStack("Worker");
transaction.commit();
}
else {
getFragmentManager().popBackStack();
mCroller.interruptMainCircleColorAnimated();
} else {
mCroller.setProgressPrimaryColor(Color.parseColor("#158b69"));
mCroller.setBackCircleColor(Color.parseColor("#158b69"));
mTextView.setTextColor(Color.parseColor("#158b69"));
mCroller.interruptMainCircleColorAnimated();
mCroller.setProgress(mMetronomBpm);
//send data to phone, that we stopped to record
sendMessage(STOP_RECORD_PATH);
mSender.sendMessage(STOP_RECORD_PATH);
getFragmentManager().popBackStack();
}
}
};
private boolean onLongPressCustomized(MotionEvent ev){
private boolean onLongPressCustomized(MotionEvent ev) {
Point currentPoint = new Point((int)ev.getX(), (int)ev.getY());
Point currentPoint = new Point((int) ev.getX(), (int) ev.getY());
//make sure the longPress only works within the maincircle of the scroller
double distancePointToMiddle = Utils.getDistance(currentPoint.x, currentPoint.y, (mDisplayWidth / 2.0f), (mDisplayHeight / 2.0f));
if ((distancePointToMiddle > mCroller.getMainCircleRadius())){
double distancePointToMiddle = Utils.getDistance(currentPoint.x, currentPoint.y, mDisplayCenter.x, mDisplayCenter.y);
if ((distancePointToMiddle > mCroller.getBackCircleRadius())) {
mHandler.removeCallbacks(mLongPressed);
return false;
}
if(ev.getAction() == MotionEvent.ACTION_DOWN){
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mHandler.postDelayed(mLongPressed, mLongPressDelay);
}
if((ev.getAction() == MotionEvent.ACTION_MOVE) || (ev.getAction() == MotionEvent.ACTION_HOVER_MOVE)){
if ((ev.getAction() == MotionEvent.ACTION_MOVE) || (ev.getAction() == MotionEvent.ACTION_HOVER_MOVE)) {
if(mPreviousMovePoint == null) {
/*
if (mPreviousMovePoint == null) {
mPreviousMovePoint = currentPoint;
}
else {
} else {
int dx = Math.abs(currentPoint.x - mPreviousMovePoint.x);
int dy = Math.abs(currentPoint.y - mPreviousMovePoint.y);
int distance = (int) Math.sqrt(dx*dx + dy*dy);
if(distance > mDistanceJitteringLongPress) {
int distance = (int) Math.sqrt(dx * dx + dy * dy);
if (distance > mDistanceJitteringLongPress) {
mHandler.removeCallbacks(mLongPressed);
return false;
}
}
}*/
}
if(ev.getAction() == MotionEvent.ACTION_UP){
if (ev.getAction() == MotionEvent.ACTION_UP) {
mHandler.removeCallbacks(mLongPressed);
if(mLongPressHandlerActivated){
if (mLongPressHandlerActivated) {
mLongPressHandlerActivated = false;
mPreviousMovePoint = null;
return true;
return false;
}
return false;
}
@@ -162,37 +144,35 @@ public class MainActivity extends WearableActivity implements WorkerFragment.OnF
return false;
}
private boolean onColorChanging(MotionEvent ev, int MainColor){
Point currentPoint = new Point((int)ev.getX(), (int)ev.getY());
private boolean onColorChanging(MotionEvent ev, int MainColor) {
Point currentPoint = new Point((int) ev.getX(), (int) ev.getY());
//only works within the maincircle of the scroller
double distancePointToMiddle = Utils.getDistance(currentPoint.x, currentPoint.y, mDisplayCenter.x, mDisplayCenter.y);
if ((distancePointToMiddle > mCroller.getMainCircleRadius())){
if ((distancePointToMiddle > mCroller.getBackCircleRadius())) {
//if we are outside the area of interest and the animation is already running, we need to reset
if(mCroller.isMainCircleAnimationRunning() || mCroller.isBackCircleAnimationRunning()){
if (mCroller.isMainCircleAnimationRunning() || mCroller.isBackCircleAnimationRunning()) {
mCroller.interruptMainCircleColorAnimated();
mCroller.interruptBackCircleAnimated();
}
else {
mCroller.setBackCircleColor(Color.parseColor("#cccccc"));
mCroller.setMainCircleColor(Color.parseColor("#ffffff"));
}
return false;
}
if(ev.getAction() == MotionEvent.ACTION_DOWN){
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//push down effect back circle (ring around main circle)
mCroller.setBackCircleColorAnimated(Color.parseColor("#cccccc"), MainColor,150);
mCroller.setBackCircleColorAnimated(Color.parseColor("#cccccc"), MainColor, 150);
//make color of main circle brighter the longer we press
mCroller.setMainCircleColorAnimated(Color.parseColor("#ffffff"), MainColor,1500);
mCroller.setMainCircleColorAnimated(Color.parseColor("#ffffff"), MainColor, 1300);
}
if(ev.getAction() == MotionEvent.ACTION_UP){
if (ev.getAction() == MotionEvent.ACTION_UP) {
//push_up effect back circle (ring around main circle)
mCroller.setBackCircleColorAnimated(MainColor, Color.parseColor("#cccccc"),150);
mCroller.setBackCircleColorAnimated(MainColor, Color.parseColor("#cccccc"), 150);
// if we remove our finger before longpress was recognized, reverse animation
mCroller.stopMainCircleColorAnimated();
@@ -201,30 +181,30 @@ public class MainActivity extends WearableActivity implements WorkerFragment.OnF
return false;
}
public boolean onTapForBpm(MotionEvent ev){
public boolean onTapForBpm(MotionEvent ev) {
Point currentPoint = new Point((int)ev.getX(), (int)ev.getY());
Point currentPoint = new Point((int) ev.getX(), (int) ev.getY());
//only works within the maincircle of the scroller
double distancePointToMiddle = Utils.getDistance(currentPoint.x, currentPoint.y, mDisplayCenter.x, mDisplayCenter.y);
if ((distancePointToMiddle > mCroller.getMainCircleRadius())){
if ((distancePointToMiddle > mCroller.getBackCircleRadius())) {
mCroller.setProgress(mCroller.getProgress());
return false;
}
if(ev.getAction() == MotionEvent.ACTION_DOWN){
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
if(!mTapRecognized){
if (!mTapRecognized) {
mTapBpm.addTimestamp(System.currentTimeMillis());
mTapRecognized = true;
}
}
if(ev.getAction() == MotionEvent.ACTION_UP) {
if (ev.getAction() == MotionEvent.ACTION_UP) {
if(!mTapBpmThread.isAlive() && mTapRecognized){
if (!mTapBpmThread.isAlive() && mTapRecognized) {
mTapBpmThread.start();
mCroller.setLabel("Tippe weiter");
}
@@ -240,20 +220,11 @@ public class MainActivity extends WearableActivity implements WorkerFragment.OnF
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//init GoogleApiClient and get nodeid (phone)
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Wearable.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
//get display infos
mDisplayWidth= this.getResources().getDisplayMetrics().widthPixels;
mDisplayHeight= this.getResources().getDisplayMetrics().heightPixels;
mDisplayWidth = this.getResources().getDisplayMetrics().widthPixels;
mDisplayHeight = this.getResources().getDisplayMetrics().heightPixels;
mDisplayCenter = new Point((mDisplayWidth / 2), (mDisplayHeight / 2));
mModeRecord = false;
mTapBpm = new TapBpm();
mTapBpm.add(this);
mTapBpmThread = new Thread(mTapBpm, "tapThread");
@@ -261,24 +232,33 @@ public class MainActivity extends WearableActivity implements WorkerFragment.OnF
mTextView = (TextView) findViewById(R.id.bpmText);
mSender = new SensorDataFileSender(this);
// circular progress bar
mCroller = (Croller) findViewById(R.id.croller);
mCroller.setOnProgressChangedListener(new Croller.onProgressChangedListener() {
@Override
public void onProgressChanged(int progress) {
// use the progress
String curProgress = Integer.toString(progress);
String metronomBpm = Integer.toString(mMetronomBpm);;
mTextView.setText(curProgress);
public void onProgressChanged(int currentBPM) {
//TODO: too many messages? or just because of thread?
if(mModeRecord){
sendMessage(START_RECORD_PATH + ":" + metronomBpm + ":" + curProgress);
}
else {
sendMessage(UPDATE_PATH + ":" + metronomBpm + ":" + curProgress);
if(currentBPM != mLastSendBPM){
mTextView.setText(Integer.toString(currentBPM));
mSender.sendMessage(UPDATE_PATH + ":" + mMetronomBpm + ":" + currentBPM);
mLastSendBPM = currentBPM;
}
if(mReadyToSend){
mReadyToSend = false;
new java.util.Timer().schedule(
new java.util.TimerTask() {
@Override
public void run() {
// your code here
mReadyToSend = true;
}
}, 100);
}
}
});
@@ -297,82 +277,67 @@ public class MainActivity extends WearableActivity implements WorkerFragment.OnF
setAmbientEnabled();
}
private void sendMessage(String Key) {
//TODO: sammel key's und schicke nur die hälfte
if (mNode != null && mGoogleApiClient!= null && mGoogleApiClient.isConnected()) {
//Log.d(TAG, "-- " + mGoogleApiClient.isConnected());
Wearable.MessageApi.sendMessage(
mGoogleApiClient, mNode.getId(), Key, new byte[0]).setResultCallback(
new ResultCallback<MessageApi.SendMessageResult>() {
@Override
public void onResult(MessageApi.SendMessageResult sendMessageResult) {
//Log.d(TAG, "-- " + sendMessageResult.getStatus().getStatusCode());
if (!sendMessageResult.getStatus().isSuccess()) {
Log.e(TAG, "Failed to send message with status code: "
+ sendMessageResult.getStatus().getStatusCode());
}
}
}
);
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//if record mode is on, we are not able to use the croller
if(mModeRecord){
if (mModeRecord) {
//TODO: jedes mal wenn ich touche ruft eine der beiden funktionen was im croller auf und der ruft neu zeichnen auf, was den progresslistener aufruf.
//bei extrem vielen berührungen des displays kackt alles ab. er zeichnet auch ständig alles neu
boolean changeColor = onColorChanging(ev, Color.parseColor("#EE693F"));
return onLongPressCustomized(ev);
}
else {
} else {
boolean changeColor = onColorChanging(ev, Color.parseColor("#158b69"));
boolean superTouch = super.dispatchTouchEvent(ev);
boolean longPress = onLongPressCustomized(ev);
boolean tapForBpm = onTapForBpm(ev);
return superTouch || longPress;
return superTouch || longPress;
}
}
@Override
public void onFragmentStopped(Vector<Double> bpmList) {
//TODO: save the bpmList into a file
//TODO: send bpmList and all other data to phone
Log.d("FragmentListener", "Received bpmList");
public void onFragmentStopped() {
//mSender.sendByteArray(sensorData);
}
@Override
protected void onStart() {
super.onStart();
if (!mResolvingError) {
mGoogleApiClient.connect();
//TODO. mGoogleApiClient.disconnect(); implementieren in onPause()
}
}
//TODO: rename this! onTapBPMNewEstimation or something like that
@Override
public void onNewEstimation(int bpm) {
protected void onResume() {
super.onResume();
mSender.wakeUpPhoneCall();
//mSender.register();
}
@Override
protected void onPause() {
super.onPause();
//mSender.unregister();
}
@Override
protected void onStop() {
super.onStop();
}
@Override
public void onNewTapEstimation(int bpm) {
mTapBpmEstimation = bpm;
synchronized (this) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if(mTapBpmEstimation > 0){
if (mTapBpmEstimation > 0) {
mCroller.setProgress(mTapBpmEstimation);
mCroller.setLabel("Fertig");
mVibrator.vibrate(10);
@@ -382,9 +347,8 @@ public class MainActivity extends WearableActivity implements WorkerFragment.OnF
}
}
//TODO: rename this! onTapBPMFinished or something like that
@Override
public void onFinished() {
public void onTapFinished() {
runOnUiThread(new Runnable() {
@Override
@@ -395,55 +359,14 @@ public class MainActivity extends WearableActivity implements WorkerFragment.OnF
mTapBpm.clearTimestamps();
mTapRecognized = false;
/*
mTapBpmThread.interrupt();
try {
mTapBpmThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
*/
}
@Override
public void onConnected(@Nullable Bundle bundle) {
resolveNode();
new Thread(new Runnable() {
@Override
public void run() {
while(mNode == null){
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
sendMessage(START_ACTIVITY_PATH);
}
}).start();
}
@Override
public void onConnectionSuspended(int i) {
//TODO: implement this
}
@Override
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
//TODO: implement this
}
private void resolveNode() {
Wearable.NodeApi.getConnectedNodes(mGoogleApiClient)
.setResultCallback(new ResultCallback<NodeApi.GetConnectedNodesResult>() {
@Override
public void onResult(NodeApi.GetConnectedNodesResult nodes) {
for (Node node : nodes.getNodes()) {
mNode = node;
}
}
});
}
}

View File

@@ -9,6 +9,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
* Created by toni on 13/11/17.
*/
//TODO: implement the metronome similar to phone. since thread sleeping is no accurate enough
public class Metronome implements Runnable{
private volatile boolean mRunning = true;
@@ -27,7 +28,12 @@ public class Metronome implements Runnable{
}
try {
Thread.sleep(60000 / mBPM);
if(mBPM > 0){
Thread.sleep(60000 / mBPM);
} else {
Thread.sleep(60000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}

View File

@@ -35,7 +35,7 @@ public class TapBpm implements Runnable {
mBpmTapped = (int) (60000 / (sumDifferenceMs / (mReceivedTabs.size() - 1)));
for (TapBpm.OnTapBpmListener listener:listeners) {
listener.onNewEstimation(mBpmTapped);
listener.onNewTapEstimation(mBpmTapped);
}
//only update everytime a new timestamp arrives
@@ -45,7 +45,7 @@ public class TapBpm implements Runnable {
for (TapBpm.OnTapBpmListener listener:listeners) {
listener.onFinished();
listener.onTapFinished();
}
}
@@ -61,8 +61,8 @@ public class TapBpm implements Runnable {
* Interface for callback calculated bpm
*/
public interface OnTapBpmListener {
void onFinished();
void onNewEstimation(int bpm);
void onTapFinished();
void onNewTapEstimation(int bpm);
}
private List<TapBpm.OnTapBpmListener> listeners = new CopyOnWriteArrayList<TapBpm.OnTapBpmListener>();

View File

@@ -22,12 +22,12 @@ import java.util.Vector;
public class WorkerFragment extends Fragment implements Metronome.OnMetronomeListener, Estimator.OnBpmEstimatorListener{
private OnFragmentInteractionListener mListener;
private Vector<Double> mBpmList; //TODO save to file.
private Vector<Double> mBpmList;
private byte[] mSensorData;
private boolean mWorkerRunning = false;
private Estimator mEstimator;
private Metronome mMetronome;
//private Thread mBpmThread;
private Thread mMetronomeThread;
private Vibrator mVibrator;
@@ -58,13 +58,12 @@ public class WorkerFragment extends Fragment implements Metronome.OnMetronomeLis
// init bpm estimator and listener
mEstimator = new Estimator(getContext());
mEstimator.add(this);
//mBpmThread = new Thread(mEstimator, "estThread");
mBpmList = new Vector<Double>();
// init metronome and listener
mMetronome = new Metronome(getArguments().getInt("bpm"));
mMetronome.add(this);
mMetronomeThread = new Thread(mMetronome, "estThread");
mMetronomeThread = new Thread(mMetronome, "metronomThread");
mVibrator = (Vibrator) this.getActivity().getSystemService(Context.VIBRATOR_SERVICE);
@@ -99,27 +98,26 @@ public class WorkerFragment extends Fragment implements Metronome.OnMetronomeLis
// start the worker thread for metronom
mMetronomeThread.start();
// everything is running
mWorkerRunning = true;
}
@Override
public void onStop() {
super.onStop();
mWorkerRunning = false;
// stop the worker thread for bpm estimator
mEstimator.stop();
// stop the worker thread for metronom
mMetronome.stop();
mMetronomeThread.interrupt();
try {
mMetronomeThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//private listener with list of all estimated bpm
if (mListener != null) {
mListener.onFragmentStopped(mBpmList);
mListener.onFragmentStopped();
}
}
@@ -147,33 +145,43 @@ public class WorkerFragment extends Fragment implements Metronome.OnMetronomeLis
//TODO: what if multiple threads access mBpmList? put into synchronized? @frank fragen :D
//TODO: send this to smartphone
if(mWorkerRunning){
if(bpm == -1){
//to stuff with UI. Write Text or make XX or something like that
}
if(bpm == -1){
//to stuff with UI. Write Text or make XX or something like that
mBpmList.add(bpm);
// we need this here, since ui elements can only be changed within activity thread and
// onNewDataAvailable lives inside the bpm estimation thread.
// TODO: is this really okay? also synchronized?
if(getActivity() != null) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
int tmpBPM = (int) (Math.round(mBpmList.lastElement()));
mTextView.setText(String.valueOf(tmpBPM));
mCroller.setProgress(tmpBPM);
}
});
}
}
}
mBpmList.add(bpm);
// we need this here, since ui elements can only be changed within activity thread and
// onNewDataAvailable lives inside the bpm estimation thread.
// TODO: is this really okay? also synchronized?
synchronized (this) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
mTextView.setText(String.valueOf(mBpmList.lastElement()));
mCroller.setProgress((int)(Math.round(mBpmList.lastElement())));
}
});
}
/**
* Callback function for the sensordata recorded while estimating bpm
*/
@Override
public void onEstimationStopped(byte[] sensorData) {
//mSensorData = sensorData;
}
/**
* Interface for connecting to WorkerFragment
*/
public interface OnFragmentInteractionListener {
void onFragmentStopped(Vector<Double> bpmList);
void onFragmentStopped();
}
}

View File

@@ -15,6 +15,13 @@ public class AccelerometerData {
this.z = z;
}
public AccelerometerData(AccelerometerData other){
this.ts = other.ts;
this.x = other.x;
this.y = other.y;
this.z = other.z;
}
@Override
public boolean equals(Object other){

View File

@@ -20,19 +20,20 @@ public class AccelerometerWindowBuffer extends ArrayList<AccelerometerData> {
//TODO: add exception handling. falseArgument if ad has no numeric x,y,z
public boolean add(AccelerometerData ad){
synchronized (this){
//do not add duplicates!
if (!isEmpty() && getYongest().equals(ad)) {
return false;
}
//do not add duplicates!
if(!isEmpty() && getYongest().equals(ad)){
return false;
boolean r = super.add(ad);
if (size() > mWindowSize) {
removeRange(0, size() - mWindowSize);
}
++mOverlapCounter;
return r;
}
boolean r = super.add(ad);
if (size() > mWindowSize){
removeRange(0, size() - mWindowSize);
}
++mOverlapCounter;
return r;
}
public boolean isNextWindowReady(){
@@ -44,6 +45,16 @@ public class AccelerometerWindowBuffer extends ArrayList<AccelerometerData> {
return false;
}
public AccelerometerWindowBuffer getFixedWindow(){
AccelerometerWindowBuffer other = new AccelerometerWindowBuffer(mWindowSize, mOverlapSize);
synchronized (this){
for(AccelerometerData data : this){
other.add(new AccelerometerData(data));
}
}
return other;
}
public AccelerometerData getYongest() {
return get(size() - 1);
}

View File

@@ -44,14 +44,15 @@ public class BpmEstimator {
//mKalman = new SimpleKalman();
}
public double estimate(){
//TODO: we use the buffer from outside.. this buffer is continuously updated.. not good!
public double estimate(AccelerometerWindowBuffer fixedWindow){
double sampleRate = mSampleRate_ms;
if(sampleRate <= 0){
sampleRate = Math.round(Utils.mean(Utils.diff(mBuffer.getTs())));
sampleRate = Math.round(Utils.mean(Utils.diff(fixedWindow.getTs())));
}
AccelerometerInterpolator interp = new AccelerometerInterpolator(mBuffer, sampleRate);
AccelerometerInterpolator interp = new AccelerometerInterpolator(fixedWindow, sampleRate);
//are we conducting?
//just look at the newest 512 samples
@@ -90,6 +91,8 @@ public class BpmEstimator {
int resetAfter = (int) Math.round(mResetLimit_ms / (mBuffer.getOverlapSize() * sampleRate));
if(++mResetCounter > resetAfter){
mBpmHistory.clear();
//TODO: send signal to clear.
mBuffer.clear();
mMvg.clear();
mResetCounter = 0;

View File

@@ -0,0 +1,154 @@
package de.tonifetzer.conductorswatch.network;
import android.content.Context;
import android.support.annotation.NonNull;
import android.util.Log;
import com.google.android.gms.tasks.Task;
import com.google.android.gms.wearable.ChannelClient;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.Wearable;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
/**
* Created by toni on 15/01/18.
*/
public class SensorDataFileSender {
private static final String START_ACTIVITY_PATH = "/start-activity";
public static final String TAG = "SensorDataFileSender";
private static final String DATA_PATH ="/data";
private Node mNode;
private boolean mChannelOpenend;
private ChannelClient.ChannelCallback mSensorDataChannelCallback;
private Context mContext;
public SensorDataFileSender(Context context){
mContext = context;
mChannelOpenend = false;
mSensorDataChannelCallback = new ChannelClient.ChannelCallback() {
@Override
public void onChannelClosed(@NonNull ChannelClient.Channel channel, int i, int i1) {
super.onChannelClosed(channel, i, i1);
//node: this happens in main thread, since anonymous abstract, to prevent make new class extending ChannelClient.ChannelCallback
mChannelOpenend = false;
Log.d(TAG, "Channel is closed on wear");
//stop showing something on saving screen :D
}
};
//find nearest smartwatch
getNearestPhone();
}
/**
* This function sends a whole SensorDataFile within a byte array.
* @param sensorData
*/
public void sendByteArray(byte[] sensorData) {
if(!mChannelOpenend){
//open channel
Wearable.getChannelClient(mContext).openChannel(mNode.getId(), DATA_PATH).addOnSuccessListener((ChannelClient.Channel openChannel) -> {
Log.d(TAG, "Data channel to phone successfully opened.");
mChannelOpenend = true;
Wearable.getChannelClient(mContext).getOutputStream(openChannel).addOnSuccessListener((OutputStream stream) -> {
synchronized (stream){
//write the data into the google service api cloud or whatever that is
try {
stream.write(sensorData);
stream.flush();
stream.close();
Log.d(TAG, "Data send and stream closed." );
} catch (IOException e) {
e.printStackTrace();
}
}
}).addOnFailureListener((Exception e) -> {
Log.d(TAG, "Unable to get outputstream : " + e);
});
}).addOnFailureListener((Exception e) -> {
Log.d(TAG, "Unable to open data channel to phone: " + e);
});
// Show something on watch "saving" screen
} else {
Log.d(TAG, "Channel is still open, that means the phone still needs time to save stuff from last recording!");
}
}
public void sendMessage(String Key) {
if (mNode != null) {
Task<Integer> sendTask = Wearable.getMessageClient(mContext).sendMessage(mNode.getId(), Key, new byte[0]);
sendTask.addOnSuccessListener((Integer i) -> {
//Log.d(TAG, "Sending msg success: " + i);
}).addOnFailureListener((Exception e) -> {
Log.d(TAG, "Sending msg failed with error: " + e);
});
} else {
Log.d(TAG, "Phone not found!");
//search again for phones within the area
//TODO: implement this using a timer. search every 10 seconds or something
//getNearestPhone();
}
}
public void getNearestPhone() {
mNode = null;
Task<List<Node>> nodeTask = Wearable.getNodeClient(mContext).getConnectedNodes();
nodeTask.addOnCompleteListener((Task<List<Node>> task) -> {
List<Node> nodes = task.getResult();
Node bestNodeId = null;
//TODO: das ist humbug, bestnodid wird immergenommen nie nearby
for (Node node : nodes) {
if (node.isNearby()) {
mNode = node;
}
bestNodeId = node;
}
mNode = bestNodeId;
wakeUpPhoneCall();
});
}
public void wakeUpPhoneCall(){
//start the app on smartphone
if(mNode != null){
sendMessage(START_ACTIVITY_PATH);
}
}
public void register(){
Wearable.getChannelClient(mContext).registerChannelCallback(mSensorDataChannelCallback);
}
public void unregister(){
Wearable.getChannelClient(mContext).unregisterChannelCallback(mSensorDataChannelCallback);
}
}

View File

@@ -0,0 +1,163 @@
package de.tonifetzer.conductorswatch.network;
import android.content.Context;
import android.support.annotation.NonNull;
import android.util.Log;
import com.google.android.gms.tasks.Task;
import com.google.android.gms.wearable.ChannelClient;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.Wearable;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* Created by toni on 15/01/18.
*/
public class SensorDataFileStreamer implements Runnable{
private static final String TAG = "SensorDataFileStreamer";
private static final String DATA_PATH ="/data";
private volatile boolean mRunning = true;
private Node mNode;
private boolean mChannelOpenend;
private boolean mOutputStreamOpened;
private ChannelClient.ChannelCallback mSensorDataChannelCallback;
private Context mContext;
private OutputStream mOutputStream;
private final BlockingQueue<byte[]> mQueue = new LinkedBlockingQueue<>();
public SensorDataFileStreamer(Context context){
mContext = context;
mChannelOpenend = false;
mOutputStreamOpened = false;
mSensorDataChannelCallback = new ChannelClient.ChannelCallback() {
@Override
public void onChannelClosed(@NonNull ChannelClient.Channel channel, int i, int i1) {
super.onChannelClosed(channel, i, i1);
//node: this happens in main thread, since anonymous abstract, to prevent make new class extending ChannelClient.ChannelCallback
mChannelOpenend = false;
mOutputStreamOpened = false;
Log.d(TAG, "Channel is closed on wear");
Wearable.getChannelClient(mContext).unregisterChannelCallback(mSensorDataChannelCallback);
}
};
//find nearest smartwatch and open channel
getNearestPhone();
}
@Override
public void run() {
while (mRunning) {
if (mOutputStreamOpened && mChannelOpenend) {
try {
//TODO: einfach bytestreamwriter direkt hier implementieren. also moutputstream in datastream(moutputstream) und dann direkt über writeint.
//TODO: channel is closed before stream was finished
mOutputStream.write(mQueue.take());
//TODO: flush after specific file size
//mOutputStream.flush();
} catch (IOException | InterruptedException e) {
mOutputStreamOpened = false;
//TODO: do something here!
e.printStackTrace();
}
} else {
//open();
//TODO: check somewhere if we have a phone or try to reconnect
}
}
}
/**
* This function sends a byte array.
* @param sensorData
*/
public void sendByteArray(byte[] sensorData) {
if(mRunning && mOutputStreamOpened && mChannelOpenend) {
mQueue.offer(sensorData);
}
}
private void getNearestPhone() {
mNode = null;
Task<List<Node>> nodeTask = Wearable.getNodeClient(mContext).getConnectedNodes();
nodeTask.addOnCompleteListener((Task<List<Node>> task) -> {
List<Node> nodes = task.getResult();
Node bestNodeId = null;
for (Node node : nodes) {
if (node.isNearby()) {
bestNodeId = node;
break;
}
bestNodeId = node;
}
mNode = bestNodeId;
if(mNode != null){
open();
}
});
}
private void open(){
if(!mChannelOpenend && mNode != null){
//register callback
Wearable.getChannelClient(mContext).registerChannelCallback(mSensorDataChannelCallback);
//open channel
Wearable.getChannelClient(mContext).openChannel(mNode.getId(), DATA_PATH).addOnSuccessListener((ChannelClient.Channel openChannel) -> {
Log.d(TAG, "Data channel to phone successfully opened.");
mChannelOpenend = true;
Wearable.getChannelClient(mContext).getOutputStream(openChannel).addOnSuccessListener((OutputStream stream) -> {
mOutputStream = stream;
mOutputStreamOpened = true;
}).addOnFailureListener((Exception e) -> {
Log.d(TAG, "Unable to get outputstream : " + e);
});
}).addOnFailureListener((Exception e) -> {
Log.d(TAG, "Unable to open data channel to phone: " + e);
});
} else {
Log.d(TAG, "Channel is still open, that means the phone still needs time to save stuff from last recording!");
}
}
public void close(){
try {
mRunning = false;
mOutputStream.close();
mOutputStreamOpened = false;
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@@ -1,5 +1,7 @@
package de.tonifetzer.conductorswatch.utilities;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
@@ -8,20 +10,23 @@ import java.io.IOException;
* Created by toni on 20/12/17.
*/
public class FileWriter {
private String mFilename;
public class ByteStreamWriter {
private ByteArrayOutputStream mByteStream;
private DataOutputStream mDataStream;
FileWriter(String filename){
mFilename = filename;
private final long mFirstTimestamp;
int mDataCounter;
public ByteStreamWriter(){
mByteStream = new ByteArrayOutputStream();
mDataStream = new DataOutputStream(mByteStream);
mDataCounter = 0;
mFirstTimestamp = System.currentTimeMillis();
}
public void writeDouble(double data){
private void writeDouble(double data){
try {
mDataStream.writeDouble(data);
} catch (IOException e) {
@@ -29,15 +34,15 @@ public class FileWriter {
}
}
public void writeString(String data){
private void writeChar(char data){
try {
mDataStream.writeChars(data);
mDataStream.writeChar(data);
} catch (IOException e) {
e.printStackTrace();
}
}
public void writeInt(int data){
private void writeInt(int data){
try {
mDataStream.writeInt(data);
} catch (IOException e) {
@@ -45,12 +50,30 @@ public class FileWriter {
}
}
public byte[] getByteArray(){
return mByteStream.toByteArray();
private void writeLong(long data){
try {
mDataStream.writeLong(data);
} catch (IOException e) {
e.printStackTrace();
}
}
public String getFilename(){
return mFilename;
public void writeSensor3D(int type, double x, double y, double z){
synchronized (this){
long writeTime = System.currentTimeMillis() - mFirstTimestamp;
writeLong(writeTime);
writeInt(type);
writeDouble(x);
writeDouble(y);
writeDouble(z);
++mDataCounter;
}
}
public byte[] getByteArray(){
//Log.d("DataLayerListenerService", "Num data written: " + mDataCounter);
return mByteStream.toByteArray();
}
public void reset(){
@@ -61,6 +84,8 @@ public class FileWriter {
try {
mByteStream.close();
mDataStream.close();
mDataCounter = 0;
} catch (IOException e) {
e.printStackTrace();
}

View File

@@ -46,7 +46,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="240"
android:text="80"
android:textStyle="bold"
android:textColor="#158b69"
android:textSize="40sp"/>