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,10 +8,11 @@ android {
compileSdkVersion 26
defaultConfig {
applicationId "de.tonifetzer.conductorswatch"
minSdkVersion 23
minSdkVersion 24
targetSdkVersion 26
versionCode 1
versionName "1.0"
//sdk 2 | product version 3 | build num 2 | multi-apk 2
versionCode 260120100
versionName "0.1.2"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
@@ -20,6 +21,10 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {

View File

@@ -2,6 +2,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.tonifetzer.conductorswatch">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
@@ -16,6 +19,7 @@
<service android:name=".DataLayerListenerService">
<intent-filter>
<action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
<action android:name="com.google.android.gms.wearable.CHANNEL_EVENT" />
<data android:scheme="wear" android:host="*" android:pathPrefix="/start-activity"/>
</intent-filter>
</service>

View File

@@ -0,0 +1,57 @@
package de.tonifetzer.conductorswatch;
/**
* Created by toni on 12/01/18.
*/
import android.content.Context;
import android.os.Environment;
import android.util.Log;
import java.io.File;
/**
* Class: DataFolder
* Description: SDK save file class. Is able to open a folder on the device independent of the given
* device and android skd version.
*/
public class DataFolder {
private File folder;
private static final String TAG = "DataFolder";
public DataFolder(Context context, String folderName){
// 1) try external data folder
folder = new File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), folderName);
if (isOK(folder)) {return;}
// 2) try sd-card folder
folder = new File(Environment.getExternalStorageDirectory() + "/" + folderName);
if (isOK(folder)) {return;}
// 3) try internal data folder
folder = new File(context.getApplicationInfo().dataDir);
if (isOK(folder)) {return;}
// all failed
throw new RuntimeException("failed to create/access storage folder");
}
/** ensure the given folder is OK */
private static final boolean isOK(final File folder) {
folder.mkdirs();
final boolean ok = folder.exists() && folder.isDirectory();
if (ok) {
Log.d(TAG, "using: " + folder);
} else {
Log.d(TAG, "not OK: " + folder);
}
return ok;
}
public File getFolder(){
return folder;
}
}

View File

@@ -3,10 +3,17 @@ package de.tonifetzer.conductorswatch;
import android.content.Intent;
import android.util.Log;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.wearable.ChannelClient;
import com.google.android.gms.wearable.MessageEvent;
import com.google.android.gms.wearable.Wearable;
import com.google.android.gms.wearable.WearableListenerService;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Listens to DataItems and Messages from the local node.
*/
@@ -14,6 +21,7 @@ public class DataLayerListenerService extends WearableListenerService {
private static final String TAG = "DataLayerService";
private static final String START_ACTIVITY_PATH = "/start-activity";
private static final String DATA_PATH ="/data";
GoogleApiClient mGoogleApiClient;
@@ -28,7 +36,7 @@ public class DataLayerListenerService extends WearableListenerService {
@Override
public void onMessageReceived(MessageEvent messageEvent) {
//LOGD(TAG, "onMessageReceived: " + messageEvent);
LOGD(TAG, "onMessageReceived: " + messageEvent);
// Check to see if the message is to start an activity
if (messageEvent.getPath().equals(START_ACTIVITY_PATH)) {
@@ -43,4 +51,40 @@ public class DataLayerListenerService extends WearableListenerService {
Log.d(tag, message);
}
}
@Override
public void onOutputClosed(ChannelClient.Channel openChannel, int i, int i1){
LOGD(TAG, "Channel successfully opened.");
//get the sensordata provided by the watch
Wearable.getChannelClient(this).getInputStream(openChannel).addOnSuccessListener((InputStream stream) -> {
Log.d(TAG, "Channel successfully opened and ready to write file.");
byte[] data = null;
try {
stream.read(data);
DataFolder folder = new DataFolder(this, "sensorOutFiles");
File file = new File(folder.getFolder(), System.currentTimeMillis() + ".csv");
try {
FileOutputStream fos = new FileOutputStream(file);
fos.write(data);
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
}).addOnFailureListener((Exception e) -> {
//Channel not open? couldn't receive inputstream?
});
//we have what we wanted, so close the channel
Wearable.getChannelClient(this).close(openChannel);
}
}

View File

@@ -1,22 +1,25 @@
package de.tonifetzer.conductorswatch;
import android.app.Activity;
import android.graphics.Color;
import android.media.MediaPlayer;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.SoundEffectConstants;
import android.widget.EditText;
import android.widget.TextView;
import com.google.android.gms.wearable.ChannelClient;
import com.google.android.gms.wearable.MessageEvent;
import com.google.android.gms.wearable.MessageClient;
import com.google.android.gms.wearable.Wearable;
import java.util.Timer;
import de.tonifetzer.conductorswatch.network.SensorDataCallback;
public class MainActivity extends AppCompatActivity implements MessageClient.OnMessageReceivedListener{
private TextView mTextView;
Timer mTimer;
private EditText mEditView;
private Timer mTimer;
private static final String TAG = "DataLayerService";
private static final String START_ACTIVITY_PATH = "/start-activity";
@@ -26,27 +29,34 @@ public class MainActivity extends AppCompatActivity implements MessageClient.OnM
private boolean mIsRecording = false;
private ChannelClient.ChannelCallback mChannelCallback;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.bpmText);
mTextView = findViewById(R.id.bpmText);
mEditView = findViewById(R.id.comments);
// handels everything regarding sensor data delivered by the watch.
mChannelCallback = new SensorDataCallback(this);
}
@Override
public void onResume() {
super.onResume();
Wearable.getMessageClient(this).addListener(this);
Wearable.getChannelClient(this).registerChannelCallback(mChannelCallback);
}
@Override
protected void onPause() {
super.onPause();
Wearable.getMessageClient(this).removeListener(this);
Wearable.getChannelClient(this).unregisterChannelCallback(mChannelCallback);
}
@Override
public void onMessageReceived(MessageEvent messageEvent) {
@@ -67,6 +77,8 @@ public class MainActivity extends AppCompatActivity implements MessageClient.OnM
mTimer = new Timer();
mTimer.scheduleAtFixedRate(new Metronome(this) , 0, 60000 / Integer.parseInt(parts[1]));
mTextView.setTextColor(Color.parseColor("#EE693F"));
mEditView.setEnabled(false);
mEditView.setFocusable(false);
mIsRecording = true;
}
@@ -75,6 +87,9 @@ public class MainActivity extends AppCompatActivity implements MessageClient.OnM
mTimer.cancel();
mTextView.setTextColor(Color.parseColor("#158b69"));
mEditView.setEnabled(true);
mEditView.setFocusable(true);
mEditView.setFocusableInTouchMode(true);
mIsRecording = false;
}
else if (messageEvent.getPath().contains(UPDATE_PATH)){

View File

@@ -0,0 +1,68 @@
package de.tonifetzer.conductorswatch.network;
import android.content.Context;
import android.support.annotation.NonNull;
import android.util.Log;
import com.google.android.gms.wearable.ChannelClient;
import com.google.android.gms.wearable.Wearable;
import java.io.InputStream;
/**
* Created by toni on 14/01/18.
*/
public class SensorDataCallback extends ChannelClient.ChannelCallback implements SensorDataFileReceiver.OnFileReceiveListener{
private static final String TAG = "SensorDataCallback";
private Context mContext;
private SensorDataFileReceiver mFileReceiver;
private ChannelClient.Channel mOpenChannel;
public SensorDataCallback(Context context) {
mContext = context;
mOpenChannel = null;
mFileReceiver = new SensorDataFileReceiver(mContext);
mFileReceiver.add(this);
}
@Override
public void onChannelOpened(@NonNull ChannelClient.Channel openChannel) {
super.onChannelOpened(openChannel);
mOpenChannel = openChannel;
Wearable.getChannelClient(mContext).getInputStream(mOpenChannel).addOnSuccessListener((InputStream stream) -> {
mFileReceiver.setOpenStream(stream);
new Thread(mFileReceiver, "FileReceiverThread").start();
});
}
@Override
public void onChannelClosed(@NonNull ChannelClient.Channel openChannel, int closeReason, int appSpecificErrorCode) {
super.onChannelClosed(openChannel, closeReason, appSpecificErrorCode);
Log.d(TAG, "Channel is closed. Reason: " + closeReason + " - ErrorCode: " + appSpecificErrorCode);
}
@Override
public void onStartSaveFile() {
//TODO: runOnUIThread stuff
Log.d(TAG, "Start saving file. - " + Thread.currentThread().getName());
}
@Override
public void onFinishedSavingFile() {
//close the channel
if(mOpenChannel != null){
Wearable.getChannelClient(mContext).close(mOpenChannel);
}
Log.d(TAG, "Finished saving file. - " + Thread.currentThread().getName());
}
}

View File

@@ -0,0 +1,111 @@
package de.tonifetzer.conductorswatch.network;
import android.content.Context;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Created by toni on 14/01/18.
*/
public class SensorDataFileReceiver implements Runnable {
private InputStream mOpenStream;
private Context mContext;
private static final String TAG = "SensorDataFileReceiver";
public SensorDataFileReceiver(Context context){
mOpenStream = null;
mContext = context;
}
public void setOpenStream(InputStream stream){
mOpenStream = stream;
}
@Override
public void run() {
if(mOpenStream != null){
Log.d(TAG, "Opened InputStream in Thread: " + Thread.currentThread().getName());
for(SensorDataFileReceiver.OnFileReceiveListener listener : listeners){
listener.onStartSaveFile();
}
byte[] bytesTimestamp = new byte[Long.BYTES];
byte[] bytesSensorID = new byte[Integer.BYTES];
byte[] bytesX= new byte[Double.BYTES];
byte[] bytesY= new byte[Double.BYTES];
byte[] bytesZ= new byte[Double.BYTES];
long ts = 0;
int id = 0;
double x = 0, y = 0, z = 0;
int i = 0;
SensorDataFileWriter fileWriter = new SensorDataFileWriter(mContext);
try {
while(mOpenStream.read(bytesTimestamp) != -1) {
ts = ByteBuffer.wrap(bytesTimestamp).getLong();
mOpenStream.read(bytesSensorID);
id = ByteBuffer.wrap(bytesSensorID).getInt();
mOpenStream.read(bytesX);
x = ByteBuffer.wrap(bytesX).getDouble();
mOpenStream.read(bytesY);
y = ByteBuffer.wrap(bytesY).getDouble();
mOpenStream.read(bytesZ);
z = ByteBuffer.wrap(bytesZ).getDouble();
++i;
if(id != 4 && id != 10){
Log.d(TAG, "STOOOOOP");
}
fileWriter.writeVector3D(ts, id, x, y, z);
}
//we have what we wanted, so close the channel
fileWriter.toDisk();
Log.d(TAG, "Num Data transferred: " + i);
//finished callback
for(SensorDataFileReceiver.OnFileReceiveListener listener : listeners){
listener.onFinishedSavingFile();
}
//close the stream, since this is recommended
mOpenStream.close();
} catch (IOException e) {
e.printStackTrace();
}
} else {
Log.d(TAG, "Channel is not valid, is it really open? Check connection to watch! - Thread: " + Thread.currentThread().getName());
}
}
/**
* Interface for callback
*/
public interface OnFileReceiveListener {
void onStartSaveFile();
void onFinishedSavingFile();
}
private List<OnFileReceiveListener> listeners = new CopyOnWriteArrayList<OnFileReceiveListener>();
public void add(SensorDataFileReceiver.OnFileReceiveListener listener){listeners.add(listener);}
public void remove(SensorDataFileReceiver.OnFileReceiveListener listener){listeners.remove(listener);}
}

View File

@@ -0,0 +1,139 @@
package de.tonifetzer.conductorswatch.network;
import android.app.Activity;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.EditText;
import android.widget.TextView;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import de.tonifetzer.conductorswatch.DataFolder;
import de.tonifetzer.conductorswatch.R;
/**
* Created by toni on 14/01/18.
*/
public class SensorDataFileWriter {
private static final int FLUSH_LIMIT = 2*1024*1024;
private static final String TAG = "SensorDataFileWriter";
private File mSensorDataFile;
private FileOutputStream mFileOutputStream;
private StringBuilder mStringBuilder = new StringBuilder();
private final DataFolder mFolder;
private boolean mStreamOpenend = false;
public SensorDataFileWriter(Context context) {
mFolder = new DataFolder(context, "sensorOutFiles");
//write some description for this file in the first line
EditText editView = ((Activity) context).findViewById(R.id.comments);
TextView textView = ((Activity) context).findViewById(R.id.bpmText);
writeDescription("Kommentar: " + editView.getText().toString() + "\nMetronom: " + textView.getText().toString() + " bpm");
//open connection to file
open();
}
public final void writeVector3D(long ts, int id, double x, double y, double z){
synchronized (this) {
if (mStreamOpenend) {
mStringBuilder.append(ts).append(';').append(id).append(';').append(x).append(';').append(y).append(';').append(z).append('\n');
if (mStringBuilder.length() > FLUSH_LIMIT) {flush(false);}
} else {
open();
}
}
}
private final void writeDescription(String str){
synchronized (this) {
mStringBuilder.append(str).append('\n').append('\n');
}
}
// flush data to disk
public final void toDisk(){
synchronized (this) {
flush(true);
close();
}
}
/** helper method for exception-less writing. DO NOT CALL DIRECTLY! */
private final void _write(final byte[] data) {
try {
mFileOutputStream.write(data);
Log.d(TAG, "flushed " + data.length + " bytes to disk");
} catch (final Exception e) {
throw new RuntimeException("error while writing log-file", e);
}
}
/** helper-class for background writing */
class FlushAsync extends AsyncTask<byte[], Integer, Integer> {
@Override
protected final Integer doInBackground(byte[][] data) {
_write(data[0]);
return null;
}
};
/** flush current buffer-contents to disk */
private final void flush(boolean sync) {
// fetch current buffer contents to write and hereafter empty the buffer
// this action MUST be atomic, just like the add-method
byte[] data = null;
synchronized (this) {
data = mStringBuilder.toString().getBytes(); // fetch data to write
mStringBuilder.setLength(0); // reset the buffer
}
// write
if (sync) {
// write to disk using the current thread
_write(data);
} else {
// write to disk using a background-thread
new FlushAsync().execute(new byte[][] {data});
}
}
private final void open() {
mSensorDataFile = new File(mFolder.getFolder(), System.currentTimeMillis() + ".csv");
try {
mFileOutputStream = new FileOutputStream(mSensorDataFile);
mStreamOpenend = true;
Log.d(TAG, "will write to: " + mSensorDataFile.toString());
} catch (FileNotFoundException e) {
e.printStackTrace();
mStreamOpenend = false;
}
}
private final void close() {
try {
mFileOutputStream.close();
mStreamOpenend = false;
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@@ -1,23 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="de.tonifetzer.conductorswatch.MainActivity">
android:orientation="vertical"
tools:context="de.tonifetzer.conductorswatch.MainActivity"
android:gravity="center">
<TextView
android:id="@+id/bpmText"
android:soundEffectsEnabled="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:soundEffectsEnabled="true"
android:text="80"
android:textStyle="bold"
android:textColor="#158b69"
android:textSize="100sp"/>
android:textSize="100sp"
android:textStyle="bold"
android:layout_centerHorizontal="true"/>
</android.support.constraint.ConstraintLayout>
<TextView
android:id="@+id/Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:soundEffectsEnabled="true"
android:text="Kommentare zur Aufnahme:"
android:textStyle="bold" />
<EditText
android:id="@+id/comments"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textMultiLine"
android:text=""
android:layout_centerHorizontal="true"/>
</LinearLayout>

View File

@@ -1,7 +1,7 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>

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"/>