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:
@@ -8,10 +8,11 @@ android {
|
|||||||
compileSdkVersion 26
|
compileSdkVersion 26
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "de.tonifetzer.conductorswatch"
|
applicationId "de.tonifetzer.conductorswatch"
|
||||||
minSdkVersion 23
|
minSdkVersion 24
|
||||||
targetSdkVersion 26
|
targetSdkVersion 26
|
||||||
versionCode 1
|
//sdk 2 | product version 3 | build num 2 | multi-apk 2
|
||||||
versionName "1.0"
|
versionCode 260120100
|
||||||
|
versionName "0.1.2"
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -20,6 +21,10 @@ android {
|
|||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="de.tonifetzer.conductorswatch">
|
package="de.tonifetzer.conductorswatch">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
@@ -16,6 +19,7 @@
|
|||||||
<service android:name=".DataLayerListenerService">
|
<service android:name=".DataLayerListenerService">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
|
<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"/>
|
<data android:scheme="wear" android:host="*" android:pathPrefix="/start-activity"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,10 +3,17 @@ package de.tonifetzer.conductorswatch;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import com.google.android.gms.common.api.GoogleApiClient;
|
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.MessageEvent;
|
||||||
import com.google.android.gms.wearable.Wearable;
|
import com.google.android.gms.wearable.Wearable;
|
||||||
import com.google.android.gms.wearable.WearableListenerService;
|
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.
|
* 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 TAG = "DataLayerService";
|
||||||
private static final String START_ACTIVITY_PATH = "/start-activity";
|
private static final String START_ACTIVITY_PATH = "/start-activity";
|
||||||
|
private static final String DATA_PATH ="/data";
|
||||||
|
|
||||||
GoogleApiClient mGoogleApiClient;
|
GoogleApiClient mGoogleApiClient;
|
||||||
|
|
||||||
@@ -28,7 +36,7 @@ public class DataLayerListenerService extends WearableListenerService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMessageReceived(MessageEvent messageEvent) {
|
public void onMessageReceived(MessageEvent messageEvent) {
|
||||||
//LOGD(TAG, "onMessageReceived: " + messageEvent);
|
LOGD(TAG, "onMessageReceived: " + messageEvent);
|
||||||
|
|
||||||
// Check to see if the message is to start an activity
|
// Check to see if the message is to start an activity
|
||||||
if (messageEvent.getPath().equals(START_ACTIVITY_PATH)) {
|
if (messageEvent.getPath().equals(START_ACTIVITY_PATH)) {
|
||||||
@@ -43,4 +51,40 @@ public class DataLayerListenerService extends WearableListenerService {
|
|||||||
Log.d(tag, message);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,22 +1,25 @@
|
|||||||
package de.tonifetzer.conductorswatch;
|
package de.tonifetzer.conductorswatch;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.media.MediaPlayer;
|
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.SoundEffectConstants;
|
import android.widget.EditText;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import com.google.android.gms.wearable.ChannelClient;
|
||||||
import com.google.android.gms.wearable.MessageEvent;
|
import com.google.android.gms.wearable.MessageEvent;
|
||||||
import com.google.android.gms.wearable.MessageClient;
|
import com.google.android.gms.wearable.MessageClient;
|
||||||
import com.google.android.gms.wearable.Wearable;
|
import com.google.android.gms.wearable.Wearable;
|
||||||
|
|
||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
|
|
||||||
|
import de.tonifetzer.conductorswatch.network.SensorDataCallback;
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity implements MessageClient.OnMessageReceivedListener{
|
public class MainActivity extends AppCompatActivity implements MessageClient.OnMessageReceivedListener{
|
||||||
|
|
||||||
private TextView mTextView;
|
private TextView mTextView;
|
||||||
Timer mTimer;
|
private EditText mEditView;
|
||||||
|
private Timer mTimer;
|
||||||
|
|
||||||
private static final String TAG = "DataLayerService";
|
private static final String TAG = "DataLayerService";
|
||||||
private static final String START_ACTIVITY_PATH = "/start-activity";
|
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 boolean mIsRecording = false;
|
||||||
|
|
||||||
|
private ChannelClient.ChannelCallback mChannelCallback;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_main);
|
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
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
Wearable.getMessageClient(this).addListener(this);
|
Wearable.getMessageClient(this).addListener(this);
|
||||||
|
Wearable.getChannelClient(this).registerChannelCallback(mChannelCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPause() {
|
protected void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
Wearable.getMessageClient(this).removeListener(this);
|
Wearable.getMessageClient(this).removeListener(this);
|
||||||
|
Wearable.getChannelClient(this).unregisterChannelCallback(mChannelCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMessageReceived(MessageEvent messageEvent) {
|
public void onMessageReceived(MessageEvent messageEvent) {
|
||||||
|
|
||||||
@@ -67,6 +77,8 @@ public class MainActivity extends AppCompatActivity implements MessageClient.OnM
|
|||||||
mTimer = new Timer();
|
mTimer = new Timer();
|
||||||
mTimer.scheduleAtFixedRate(new Metronome(this) , 0, 60000 / Integer.parseInt(parts[1]));
|
mTimer.scheduleAtFixedRate(new Metronome(this) , 0, 60000 / Integer.parseInt(parts[1]));
|
||||||
mTextView.setTextColor(Color.parseColor("#EE693F"));
|
mTextView.setTextColor(Color.parseColor("#EE693F"));
|
||||||
|
mEditView.setEnabled(false);
|
||||||
|
mEditView.setFocusable(false);
|
||||||
mIsRecording = true;
|
mIsRecording = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +87,9 @@ public class MainActivity extends AppCompatActivity implements MessageClient.OnM
|
|||||||
|
|
||||||
mTimer.cancel();
|
mTimer.cancel();
|
||||||
mTextView.setTextColor(Color.parseColor("#158b69"));
|
mTextView.setTextColor(Color.parseColor("#158b69"));
|
||||||
|
mEditView.setEnabled(true);
|
||||||
|
mEditView.setFocusable(true);
|
||||||
|
mEditView.setFocusableInTouchMode(true);
|
||||||
mIsRecording = false;
|
mIsRecording = false;
|
||||||
}
|
}
|
||||||
else if (messageEvent.getPath().contains(UPDATE_PATH)){
|
else if (messageEvent.getPath().contains(UPDATE_PATH)){
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +1,38 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="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
|
<TextView
|
||||||
android:id="@+id/bpmText"
|
android:id="@+id/bpmText"
|
||||||
android:soundEffectsEnabled="true"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
android:soundEffectsEnabled="true"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
android:text="80"
|
android:text="80"
|
||||||
android:textStyle="bold"
|
|
||||||
android:textColor="#158b69"
|
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>
|
||||||
|
|||||||
BIN
android/ConductorsPhone/app/src/main/res/raw/metronom.flac
Normal file
BIN
android/ConductorsPhone/app/src/main/res/raw/metronom.flac
Normal file
Binary file not shown.
BIN
android/ConductorsPhone/app/src/main/res/raw/metronom2.wav
Normal file
BIN
android/ConductorsPhone/app/src/main/res/raw/metronom2.wav
Normal file
Binary file not shown.
BIN
android/ConductorsPhone/app/src/main/res/raw/metronom3.wav
Normal file
BIN
android/ConductorsPhone/app/src/main/res/raw/metronom3.wav
Normal file
Binary file not shown.
@@ -1,7 +1,7 @@
|
|||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<!-- Base application theme. -->
|
<!-- Base application theme. -->
|
||||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||||
<!-- Customize your theme here. -->
|
<!-- Customize your theme here. -->
|
||||||
<item name="colorPrimary">@color/colorPrimary</item>
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ android {
|
|||||||
applicationId "de.tonifetzer.conductorswatch"
|
applicationId "de.tonifetzer.conductorswatch"
|
||||||
minSdkVersion 23
|
minSdkVersion 23
|
||||||
targetSdkVersion 26
|
targetSdkVersion 26
|
||||||
versionCode 1
|
//sdk 2 | product version 3 | build num 2 | multi-apk 2
|
||||||
versionName "1.0"
|
versionCode 260120101
|
||||||
|
versionName "0.1.2"
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
|
|||||||
@@ -231,6 +231,7 @@ public class Croller extends View {
|
|||||||
protected void onDraw(Canvas canvas) {
|
protected void onDraw(Canvas canvas) {
|
||||||
super.onDraw(canvas);
|
super.onDraw(canvas);
|
||||||
|
|
||||||
|
//called way to much!
|
||||||
if (mProgressChangeListener != null)
|
if (mProgressChangeListener != null)
|
||||||
mProgressChangeListener.onProgressChanged((int) (deg - 2));
|
mProgressChangeListener.onProgressChanged((int) (deg - 2));
|
||||||
|
|
||||||
@@ -502,8 +503,10 @@ public class Croller extends View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setProgress(int x) {
|
public void setProgress(int x) {
|
||||||
deg = x + 2;
|
if(deg != x + 2){
|
||||||
invalidate();
|
deg = x + 2;
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getLabel() {
|
public String getLabel() {
|
||||||
@@ -511,8 +514,10 @@ public class Croller extends View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setLabel(String txt) {
|
public void setLabel(String txt) {
|
||||||
label = txt;
|
if(!label.equals(txt)){
|
||||||
invalidate();
|
label = txt;
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getBackCircleColor() {
|
public int getBackCircleColor() {
|
||||||
@@ -520,8 +525,10 @@ public class Croller extends View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setBackCircleColor(int backCircleColor) {
|
public void setBackCircleColor(int backCircleColor) {
|
||||||
this.backCircleColor = backCircleColor;
|
if(this.backCircleColor != backCircleColor){
|
||||||
invalidate();
|
this.backCircleColor = backCircleColor;
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getMainCircleColor() {
|
public int getMainCircleColor() {
|
||||||
@@ -529,8 +536,10 @@ public class Croller extends View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setMainCircleColor(int mainCircleColor) {
|
public void setMainCircleColor(int mainCircleColor) {
|
||||||
this.mainCircleColor = mainCircleColor;
|
if(this.mainCircleColor != mainCircleColor){
|
||||||
invalidate();
|
this.mainCircleColor = mainCircleColor;
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ValueAnimator mainCircleAnimation;
|
private ValueAnimator mainCircleAnimation;
|
||||||
|
|||||||
@@ -6,50 +6,40 @@ import android.hardware.SensorEvent;
|
|||||||
import android.hardware.SensorEventListener;
|
import android.hardware.SensorEventListener;
|
||||||
import android.hardware.SensorManager;
|
import android.hardware.SensorManager;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
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.AccelerometerData;
|
||||||
import de.tonifetzer.conductorswatch.bpmEstimation.AccelerometerWindowBuffer;
|
import de.tonifetzer.conductorswatch.bpmEstimation.AccelerometerWindowBuffer;
|
||||||
import de.tonifetzer.conductorswatch.bpmEstimation.BpmEstimator;
|
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.
|
* Created by toni on 13/11/17.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//TODO: einfügen der logik autoCorr + FindPeaks
|
|
||||||
public class Estimator implements SensorEventListener {
|
public class Estimator implements SensorEventListener {
|
||||||
|
|
||||||
private SensorManager mSensorManager;
|
private SensorManager mSensorManager;
|
||||||
private Sensor mAccelerometer;
|
private Sensor mAccelerometer;
|
||||||
|
private Sensor mGyroscope;
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
private AccelerometerWindowBuffer mAccelerometerWindowBuffer;
|
private AccelerometerWindowBuffer mAccelerometerWindowBuffer;
|
||||||
private BpmEstimator mBpmEstimator;
|
private BpmEstimator mBpmEstimator;
|
||||||
|
|
||||||
private Handler mHandler;
|
private ByteStreamWriter mByteStreamWriterAcc;
|
||||||
private int mUpdaterate_ms = 100;
|
private ByteStreamWriter mByteStreamWriterGyro;
|
||||||
private boolean mSensorUpdateFlag = false;
|
private SensorDataFileStreamer mStreamer;
|
||||||
|
|
||||||
|
private Timer mTimer = new Timer();
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
public Estimator(Context mContext){
|
public Estimator(Context mContext){
|
||||||
@@ -60,36 +50,73 @@ public class Estimator implements SensorEventListener {
|
|||||||
|
|
||||||
mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
|
mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
|
||||||
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION);
|
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION);
|
||||||
|
mGyroscope = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
|
||||||
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_FASTEST);
|
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_FASTEST);
|
||||||
|
mSensorManager.registerListener(this, mGyroscope, SensorManager.SENSOR_DELAY_FASTEST);
|
||||||
|
|
||||||
mAccelerometerWindowBuffer = new AccelerometerWindowBuffer(1024, 256);
|
mAccelerometerWindowBuffer = new AccelerometerWindowBuffer(1024, 256);
|
||||||
mBpmEstimator = new BpmEstimator(mAccelerometerWindowBuffer, 0, 5000);
|
mBpmEstimator = new BpmEstimator(mAccelerometerWindowBuffer, 0, 5000);
|
||||||
|
|
||||||
mHandler = new Handler();
|
mTimer.scheduleAtFixedRate(new TimerTask() {
|
||||||
mHandler.post(mProcessSensors); //start runnable
|
@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() {
|
public void stop() {
|
||||||
mHandler.removeCallbacks(mProcessSensors); //remove runnable
|
|
||||||
|
mTimer.cancel();
|
||||||
|
mTimer.purge();
|
||||||
|
|
||||||
mSensorManager.unregisterListener(this);
|
mSensorManager.unregisterListener(this);
|
||||||
|
|
||||||
|
//send data and close the ByteStreamWriter
|
||||||
|
for (OnBpmEstimatorListener listener : listeners) {
|
||||||
|
//listener.onEstimationStopped(mByteStreamWriter.getByteArray());
|
||||||
|
}
|
||||||
|
mByteStreamWriterAcc.close();
|
||||||
|
mByteStreamWriterGyro.close();
|
||||||
|
|
||||||
|
mStreamer.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSensorChanged(SensorEvent se) {
|
public void onSensorChanged(SensorEvent se) {
|
||||||
|
|
||||||
// einkommentieren, falls die updaterate beschränkt werden soll. aktuell maximum speed
|
//TODO: at the moment this runs in main thread... since worker fragment runs also in main thread
|
||||||
//if(mSensorUpdateFlag) {
|
|
||||||
|
|
||||||
//ca 200hz, every 5 to 6 ms we have an update
|
//ca 200hz, every 5 to 6 ms we have an update
|
||||||
if (se.sensor.getType() == Sensor.TYPE_LINEAR_ACCELERATION) {
|
if (se.sensor.getType() == Sensor.TYPE_LINEAR_ACCELERATION) {
|
||||||
mAccelerometerWindowBuffer.add(new AccelerometerData(System.currentTimeMillis(), se.values[0], se.values[1], se.values[2]));
|
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
|
@Override
|
||||||
@@ -97,12 +124,12 @@ public class Estimator implements SensorEventListener {
|
|||||||
// do nothin
|
// do nothin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for callback calculated bpm
|
* Interface for callback calculated bpm
|
||||||
*/
|
*/
|
||||||
public interface OnBpmEstimatorListener {
|
public interface OnBpmEstimatorListener {
|
||||||
void onNewDataAvailable(double bpm);
|
void onNewDataAvailable(double bpm);
|
||||||
|
void onEstimationStopped(byte[] sensorData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<OnBpmEstimatorListener> listeners = new CopyOnWriteArrayList<OnBpmEstimatorListener>();
|
private List<OnBpmEstimatorListener> listeners = new CopyOnWriteArrayList<OnBpmEstimatorListener>();
|
||||||
|
|||||||
@@ -7,48 +7,29 @@ import android.graphics.Point;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Vibrator;
|
import android.os.Vibrator;
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.support.wearable.activity.WearableActivity;
|
import android.support.wearable.activity.WearableActivity;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.GestureDetector;
|
import android.view.GestureDetector;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.google.android.gms.common.ConnectionResult;
|
import de.tonifetzer.conductorswatch.network.SensorDataFileSender;
|
||||||
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.utilities.Utils;
|
import de.tonifetzer.conductorswatch.utilities.Utils;
|
||||||
|
|
||||||
public class MainActivity extends WearableActivity implements WorkerFragment.OnFragmentInteractionListener, TapBpm.OnTapBpmListener, GoogleApiClient.ConnectionCallbacks,
|
public class MainActivity extends WearableActivity implements WorkerFragment.OnFragmentInteractionListener, TapBpm.OnTapBpmListener {
|
||||||
GoogleApiClient.OnConnectionFailedListener{
|
|
||||||
|
|
||||||
// member
|
// member
|
||||||
private TextView mTextView;
|
private TextView mTextView;
|
||||||
private Croller mCroller;
|
private Croller mCroller;
|
||||||
private GestureDetector mDetector;
|
private GestureDetector mDetector;
|
||||||
private boolean mModeRecord;
|
private boolean mModeRecord = false;
|
||||||
|
|
||||||
// connection to phone stuff
|
// 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 START_RECORD_PATH = "/start-record";
|
||||||
private static final String STOP_RECORD_PATH = "/stop-record";
|
private static final String STOP_RECORD_PATH = "/stop-record";
|
||||||
private static final String UPDATE_PATH = "/update";
|
private static final String UPDATE_PATH = "/update";
|
||||||
|
private SensorDataFileSender mSender;
|
||||||
public static final String TAG = "DataLayerListenerService";
|
private volatile boolean mReadyToSend = true;
|
||||||
private GoogleApiClient mGoogleApiClient;
|
|
||||||
private Node mNode;
|
|
||||||
private boolean mResolvingError = false;
|
|
||||||
|
|
||||||
// display center
|
// display center
|
||||||
private int mDisplayWidth;
|
private int mDisplayWidth;
|
||||||
@@ -56,7 +37,8 @@ public class MainActivity extends WearableActivity implements WorkerFragment.OnF
|
|||||||
private Point mDisplayCenter;
|
private Point mDisplayCenter;
|
||||||
|
|
||||||
// saved Bpm to reset after recording
|
// saved Bpm to reset after recording
|
||||||
private int mMetronomBpm;
|
private int mMetronomBpm = 80;
|
||||||
|
private int mLastSendBPM = 80;
|
||||||
|
|
||||||
// tapping
|
// tapping
|
||||||
private Thread mTapBpmThread;
|
private Thread mTapBpmThread;
|
||||||
@@ -81,7 +63,7 @@ public class MainActivity extends WearableActivity implements WorkerFragment.OnF
|
|||||||
|
|
||||||
mModeRecord = !mModeRecord;
|
mModeRecord = !mModeRecord;
|
||||||
|
|
||||||
if(mModeRecord){
|
if (mModeRecord) {
|
||||||
WorkerFragment worker = new WorkerFragment();
|
WorkerFragment worker = new WorkerFragment();
|
||||||
|
|
||||||
//provide the fragment with the bpm set
|
//provide the fragment with the bpm set
|
||||||
@@ -90,71 +72,71 @@ public class MainActivity extends WearableActivity implements WorkerFragment.OnF
|
|||||||
worker.setArguments(args);
|
worker.setArguments(args);
|
||||||
|
|
||||||
//send data to phone, that we start to record
|
//send data to phone, that we start to record
|
||||||
String curProgress = Integer.toString(mCroller.getProgress());
|
mMetronomBpm = mCroller.getProgress();
|
||||||
sendMessage(START_RECORD_PATH + ":" + curProgress + ":" + curProgress);
|
mSender.sendMessage(START_RECORD_PATH + ":" + mMetronomBpm + ":" + mMetronomBpm);
|
||||||
|
|
||||||
//setter
|
//setter
|
||||||
worker.setCrollerWorker(mCroller);
|
worker.setCrollerWorker(mCroller);
|
||||||
worker.setTextViewWorker(mTextView);
|
worker.setTextViewWorker(mTextView);
|
||||||
mMetronomBpm = mCroller.getProgress();
|
|
||||||
|
|
||||||
//create fragment instance
|
//create fragment instance
|
||||||
FragmentTransaction transaction = getFragmentManager().beginTransaction();
|
FragmentTransaction transaction = getFragmentManager().beginTransaction();
|
||||||
transaction.replace(R.id.layout, worker, "Worker");
|
transaction.replace(R.id.layout, worker, "Worker");
|
||||||
transaction.addToBackStack("Worker");
|
transaction.addToBackStack("Worker");
|
||||||
transaction.commit();
|
transaction.commit();
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
getFragmentManager().popBackStack();
|
|
||||||
mCroller.interruptMainCircleColorAnimated();
|
|
||||||
mCroller.setProgressPrimaryColor(Color.parseColor("#158b69"));
|
mCroller.setProgressPrimaryColor(Color.parseColor("#158b69"));
|
||||||
mCroller.setBackCircleColor(Color.parseColor("#158b69"));
|
mCroller.setBackCircleColor(Color.parseColor("#158b69"));
|
||||||
mTextView.setTextColor(Color.parseColor("#158b69"));
|
mTextView.setTextColor(Color.parseColor("#158b69"));
|
||||||
|
mCroller.interruptMainCircleColorAnimated();
|
||||||
mCroller.setProgress(mMetronomBpm);
|
mCroller.setProgress(mMetronomBpm);
|
||||||
|
|
||||||
//send data to phone, that we stopped to record
|
//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
|
//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));
|
double distancePointToMiddle = Utils.getDistance(currentPoint.x, currentPoint.y, mDisplayCenter.x, mDisplayCenter.y);
|
||||||
if ((distancePointToMiddle > mCroller.getMainCircleRadius())){
|
if ((distancePointToMiddle > mCroller.getBackCircleRadius())) {
|
||||||
mHandler.removeCallbacks(mLongPressed);
|
mHandler.removeCallbacks(mLongPressed);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(ev.getAction() == MotionEvent.ACTION_DOWN){
|
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
|
||||||
mHandler.postDelayed(mLongPressed, mLongPressDelay);
|
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;
|
mPreviousMovePoint = currentPoint;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
int dx = Math.abs(currentPoint.x - mPreviousMovePoint.x);
|
int dx = Math.abs(currentPoint.x - mPreviousMovePoint.x);
|
||||||
int dy = Math.abs(currentPoint.y - mPreviousMovePoint.y);
|
int dy = Math.abs(currentPoint.y - mPreviousMovePoint.y);
|
||||||
int distance = (int) Math.sqrt(dx*dx + dy*dy);
|
int distance = (int) Math.sqrt(dx * dx + dy * dy);
|
||||||
if(distance > mDistanceJitteringLongPress) {
|
if (distance > mDistanceJitteringLongPress) {
|
||||||
mHandler.removeCallbacks(mLongPressed);
|
mHandler.removeCallbacks(mLongPressed);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
if(ev.getAction() == MotionEvent.ACTION_UP){
|
if (ev.getAction() == MotionEvent.ACTION_UP) {
|
||||||
mHandler.removeCallbacks(mLongPressed);
|
mHandler.removeCallbacks(mLongPressed);
|
||||||
if(mLongPressHandlerActivated){
|
if (mLongPressHandlerActivated) {
|
||||||
mLongPressHandlerActivated = false;
|
mLongPressHandlerActivated = false;
|
||||||
mPreviousMovePoint = null;
|
mPreviousMovePoint = null;
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -162,37 +144,35 @@ public class MainActivity extends WearableActivity implements WorkerFragment.OnF
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean onColorChanging(MotionEvent ev, int MainColor){
|
private boolean onColorChanging(MotionEvent ev, int MainColor) {
|
||||||
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
|
//only works within the maincircle of the scroller
|
||||||
double distancePointToMiddle = Utils.getDistance(currentPoint.x, currentPoint.y, mDisplayCenter.x, mDisplayCenter.y);
|
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 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.interruptMainCircleColorAnimated();
|
||||||
mCroller.interruptBackCircleAnimated();
|
mCroller.interruptBackCircleAnimated();
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
mCroller.setBackCircleColor(Color.parseColor("#cccccc"));
|
mCroller.setBackCircleColor(Color.parseColor("#cccccc"));
|
||||||
mCroller.setMainCircleColor(Color.parseColor("#ffffff"));
|
mCroller.setMainCircleColor(Color.parseColor("#ffffff"));
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(ev.getAction() == MotionEvent.ACTION_DOWN){
|
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
|
||||||
//push down effect back circle (ring around main circle)
|
//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
|
//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)
|
//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
|
// if we remove our finger before longpress was recognized, reverse animation
|
||||||
mCroller.stopMainCircleColorAnimated();
|
mCroller.stopMainCircleColorAnimated();
|
||||||
@@ -201,30 +181,30 @@ public class MainActivity extends WearableActivity implements WorkerFragment.OnF
|
|||||||
return false;
|
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
|
//only works within the maincircle of the scroller
|
||||||
double distancePointToMiddle = Utils.getDistance(currentPoint.x, currentPoint.y, mDisplayCenter.x, mDisplayCenter.y);
|
double distancePointToMiddle = Utils.getDistance(currentPoint.x, currentPoint.y, mDisplayCenter.x, mDisplayCenter.y);
|
||||||
if ((distancePointToMiddle > mCroller.getMainCircleRadius())){
|
if ((distancePointToMiddle > mCroller.getBackCircleRadius())) {
|
||||||
|
|
||||||
mCroller.setProgress(mCroller.getProgress());
|
mCroller.setProgress(mCroller.getProgress());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(ev.getAction() == MotionEvent.ACTION_DOWN){
|
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
|
||||||
|
|
||||||
if(!mTapRecognized){
|
if (!mTapRecognized) {
|
||||||
|
|
||||||
mTapBpm.addTimestamp(System.currentTimeMillis());
|
mTapBpm.addTimestamp(System.currentTimeMillis());
|
||||||
mTapRecognized = true;
|
mTapRecognized = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(ev.getAction() == MotionEvent.ACTION_UP) {
|
if (ev.getAction() == MotionEvent.ACTION_UP) {
|
||||||
|
|
||||||
if(!mTapBpmThread.isAlive() && mTapRecognized){
|
if (!mTapBpmThread.isAlive() && mTapRecognized) {
|
||||||
mTapBpmThread.start();
|
mTapBpmThread.start();
|
||||||
mCroller.setLabel("Tippe weiter");
|
mCroller.setLabel("Tippe weiter");
|
||||||
}
|
}
|
||||||
@@ -240,20 +220,11 @@ public class MainActivity extends WearableActivity implements WorkerFragment.OnF
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_main);
|
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
|
//get display infos
|
||||||
mDisplayWidth= this.getResources().getDisplayMetrics().widthPixels;
|
mDisplayWidth = this.getResources().getDisplayMetrics().widthPixels;
|
||||||
mDisplayHeight= this.getResources().getDisplayMetrics().heightPixels;
|
mDisplayHeight = this.getResources().getDisplayMetrics().heightPixels;
|
||||||
mDisplayCenter = new Point((mDisplayWidth / 2), (mDisplayHeight / 2));
|
mDisplayCenter = new Point((mDisplayWidth / 2), (mDisplayHeight / 2));
|
||||||
|
|
||||||
mModeRecord = false;
|
|
||||||
|
|
||||||
mTapBpm = new TapBpm();
|
mTapBpm = new TapBpm();
|
||||||
mTapBpm.add(this);
|
mTapBpm.add(this);
|
||||||
mTapBpmThread = new Thread(mTapBpm, "tapThread");
|
mTapBpmThread = new Thread(mTapBpm, "tapThread");
|
||||||
@@ -261,24 +232,33 @@ public class MainActivity extends WearableActivity implements WorkerFragment.OnF
|
|||||||
|
|
||||||
mTextView = (TextView) findViewById(R.id.bpmText);
|
mTextView = (TextView) findViewById(R.id.bpmText);
|
||||||
|
|
||||||
|
mSender = new SensorDataFileSender(this);
|
||||||
|
|
||||||
// circular progress bar
|
// circular progress bar
|
||||||
mCroller = (Croller) findViewById(R.id.croller);
|
mCroller = (Croller) findViewById(R.id.croller);
|
||||||
mCroller.setOnProgressChangedListener(new Croller.onProgressChangedListener() {
|
mCroller.setOnProgressChangedListener(new Croller.onProgressChangedListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onProgressChanged(int progress) {
|
public void onProgressChanged(int currentBPM) {
|
||||||
// use the progress
|
|
||||||
String curProgress = Integer.toString(progress);
|
|
||||||
String metronomBpm = Integer.toString(mMetronomBpm);;
|
|
||||||
mTextView.setText(curProgress);
|
|
||||||
|
|
||||||
//TODO: too many messages? or just because of thread?
|
if(currentBPM != mLastSendBPM){
|
||||||
if(mModeRecord){
|
|
||||||
sendMessage(START_RECORD_PATH + ":" + metronomBpm + ":" + curProgress);
|
mTextView.setText(Integer.toString(currentBPM));
|
||||||
}
|
mSender.sendMessage(UPDATE_PATH + ":" + mMetronomBpm + ":" + currentBPM);
|
||||||
else {
|
mLastSendBPM = currentBPM;
|
||||||
sendMessage(UPDATE_PATH + ":" + metronomBpm + ":" + curProgress);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
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
|
@Override
|
||||||
public boolean dispatchTouchEvent(MotionEvent ev) {
|
public boolean dispatchTouchEvent(MotionEvent ev) {
|
||||||
|
|
||||||
//if record mode is on, we are not able to use the croller
|
//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"));
|
boolean changeColor = onColorChanging(ev, Color.parseColor("#EE693F"));
|
||||||
return onLongPressCustomized(ev);
|
return onLongPressCustomized(ev);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
|
|
||||||
boolean changeColor = onColorChanging(ev, Color.parseColor("#158b69"));
|
boolean changeColor = onColorChanging(ev, Color.parseColor("#158b69"));
|
||||||
boolean superTouch = super.dispatchTouchEvent(ev);
|
boolean superTouch = super.dispatchTouchEvent(ev);
|
||||||
boolean longPress = onLongPressCustomized(ev);
|
boolean longPress = onLongPressCustomized(ev);
|
||||||
boolean tapForBpm = onTapForBpm(ev);
|
boolean tapForBpm = onTapForBpm(ev);
|
||||||
|
|
||||||
return superTouch || longPress;
|
return superTouch || longPress;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFragmentStopped(Vector<Double> bpmList) {
|
public void onFragmentStopped() {
|
||||||
|
//mSender.sendByteArray(sensorData);
|
||||||
//TODO: save the bpmList into a file
|
|
||||||
|
|
||||||
//TODO: send bpmList and all other data to phone
|
|
||||||
|
|
||||||
Log.d("FragmentListener", "Received bpmList");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStart() {
|
protected void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
if (!mResolvingError) {
|
|
||||||
mGoogleApiClient.connect();
|
|
||||||
|
|
||||||
//TODO. mGoogleApiClient.disconnect(); implementieren in onPause()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: rename this! onTapBPMNewEstimation or something like that
|
|
||||||
@Override
|
@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;
|
mTapBpmEstimation = bpm;
|
||||||
|
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if(mTapBpmEstimation > 0){
|
if (mTapBpmEstimation > 0) {
|
||||||
mCroller.setProgress(mTapBpmEstimation);
|
mCroller.setProgress(mTapBpmEstimation);
|
||||||
mCroller.setLabel("Fertig");
|
mCroller.setLabel("Fertig");
|
||||||
mVibrator.vibrate(10);
|
mVibrator.vibrate(10);
|
||||||
@@ -382,9 +347,8 @@ public class MainActivity extends WearableActivity implements WorkerFragment.OnF
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: rename this! onTapBPMFinished or something like that
|
|
||||||
@Override
|
@Override
|
||||||
public void onFinished() {
|
public void onTapFinished() {
|
||||||
|
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@@ -395,55 +359,14 @@ public class MainActivity extends WearableActivity implements WorkerFragment.OnF
|
|||||||
|
|
||||||
mTapBpm.clearTimestamps();
|
mTapBpm.clearTimestamps();
|
||||||
mTapRecognized = false;
|
mTapRecognized = false;
|
||||||
|
/*
|
||||||
mTapBpmThread.interrupt();
|
mTapBpmThread.interrupt();
|
||||||
try {
|
try {
|
||||||
mTapBpmThread.join();
|
mTapBpmThread.join();
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
e.printStackTrace();
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
|||||||
* Created by toni on 13/11/17.
|
* 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{
|
public class Metronome implements Runnable{
|
||||||
|
|
||||||
private volatile boolean mRunning = true;
|
private volatile boolean mRunning = true;
|
||||||
@@ -27,7 +28,12 @@ public class Metronome implements Runnable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Thread.sleep(60000 / mBPM);
|
if(mBPM > 0){
|
||||||
|
Thread.sleep(60000 / mBPM);
|
||||||
|
} else {
|
||||||
|
Thread.sleep(60000);
|
||||||
|
}
|
||||||
|
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public class TapBpm implements Runnable {
|
|||||||
mBpmTapped = (int) (60000 / (sumDifferenceMs / (mReceivedTabs.size() - 1)));
|
mBpmTapped = (int) (60000 / (sumDifferenceMs / (mReceivedTabs.size() - 1)));
|
||||||
|
|
||||||
for (TapBpm.OnTapBpmListener listener:listeners) {
|
for (TapBpm.OnTapBpmListener listener:listeners) {
|
||||||
listener.onNewEstimation(mBpmTapped);
|
listener.onNewTapEstimation(mBpmTapped);
|
||||||
}
|
}
|
||||||
|
|
||||||
//only update everytime a new timestamp arrives
|
//only update everytime a new timestamp arrives
|
||||||
@@ -45,7 +45,7 @@ public class TapBpm implements Runnable {
|
|||||||
|
|
||||||
|
|
||||||
for (TapBpm.OnTapBpmListener listener:listeners) {
|
for (TapBpm.OnTapBpmListener listener:listeners) {
|
||||||
listener.onFinished();
|
listener.onTapFinished();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,8 +61,8 @@ public class TapBpm implements Runnable {
|
|||||||
* Interface for callback calculated bpm
|
* Interface for callback calculated bpm
|
||||||
*/
|
*/
|
||||||
public interface OnTapBpmListener {
|
public interface OnTapBpmListener {
|
||||||
void onFinished();
|
void onTapFinished();
|
||||||
void onNewEstimation(int bpm);
|
void onNewTapEstimation(int bpm);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<TapBpm.OnTapBpmListener> listeners = new CopyOnWriteArrayList<TapBpm.OnTapBpmListener>();
|
private List<TapBpm.OnTapBpmListener> listeners = new CopyOnWriteArrayList<TapBpm.OnTapBpmListener>();
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ import java.util.Vector;
|
|||||||
public class WorkerFragment extends Fragment implements Metronome.OnMetronomeListener, Estimator.OnBpmEstimatorListener{
|
public class WorkerFragment extends Fragment implements Metronome.OnMetronomeListener, Estimator.OnBpmEstimatorListener{
|
||||||
|
|
||||||
private OnFragmentInteractionListener mListener;
|
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 Estimator mEstimator;
|
||||||
private Metronome mMetronome;
|
private Metronome mMetronome;
|
||||||
|
|
||||||
//private Thread mBpmThread;
|
|
||||||
private Thread mMetronomeThread;
|
private Thread mMetronomeThread;
|
||||||
|
|
||||||
private Vibrator mVibrator;
|
private Vibrator mVibrator;
|
||||||
@@ -58,13 +58,12 @@ public class WorkerFragment extends Fragment implements Metronome.OnMetronomeLis
|
|||||||
// init bpm estimator and listener
|
// init bpm estimator and listener
|
||||||
mEstimator = new Estimator(getContext());
|
mEstimator = new Estimator(getContext());
|
||||||
mEstimator.add(this);
|
mEstimator.add(this);
|
||||||
//mBpmThread = new Thread(mEstimator, "estThread");
|
|
||||||
mBpmList = new Vector<Double>();
|
mBpmList = new Vector<Double>();
|
||||||
|
|
||||||
// init metronome and listener
|
// init metronome and listener
|
||||||
mMetronome = new Metronome(getArguments().getInt("bpm"));
|
mMetronome = new Metronome(getArguments().getInt("bpm"));
|
||||||
mMetronome.add(this);
|
mMetronome.add(this);
|
||||||
mMetronomeThread = new Thread(mMetronome, "estThread");
|
mMetronomeThread = new Thread(mMetronome, "metronomThread");
|
||||||
|
|
||||||
mVibrator = (Vibrator) this.getActivity().getSystemService(Context.VIBRATOR_SERVICE);
|
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
|
// start the worker thread for metronom
|
||||||
mMetronomeThread.start();
|
mMetronomeThread.start();
|
||||||
|
|
||||||
|
// everything is running
|
||||||
|
mWorkerRunning = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStop() {
|
public void onStop() {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
|
|
||||||
|
mWorkerRunning = false;
|
||||||
|
|
||||||
// stop the worker thread for bpm estimator
|
// stop the worker thread for bpm estimator
|
||||||
mEstimator.stop();
|
mEstimator.stop();
|
||||||
|
|
||||||
// stop the worker thread for metronom
|
// stop the worker thread for metronom
|
||||||
mMetronome.stop();
|
mMetronome.stop();
|
||||||
mMetronomeThread.interrupt();
|
|
||||||
try {
|
|
||||||
mMetronomeThread.join();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
//private listener with list of all estimated bpm
|
//private listener with list of all estimated bpm
|
||||||
if (mListener != null) {
|
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: what if multiple threads access mBpmList? put into synchronized? @frank fragen :D
|
||||||
//TODO: send this to smartphone
|
//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){
|
mBpmList.add(bpm);
|
||||||
//to stuff with UI. Write Text or make XX or something like that
|
|
||||||
|
// 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);
|
/**
|
||||||
|
* Callback function for the sensordata recorded while estimating bpm
|
||||||
// we need this here, since ui elements can only be changed within activity thread and
|
*/
|
||||||
// onNewDataAvailable lives inside the bpm estimation thread.
|
@Override
|
||||||
// TODO: is this really okay? also synchronized?
|
public void onEstimationStopped(byte[] sensorData) {
|
||||||
synchronized (this) {
|
//mSensorData = sensorData;
|
||||||
getActivity().runOnUiThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
|
|
||||||
mTextView.setText(String.valueOf(mBpmList.lastElement()));
|
|
||||||
mCroller.setProgress((int)(Math.round(mBpmList.lastElement())));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for connecting to WorkerFragment
|
* Interface for connecting to WorkerFragment
|
||||||
*/
|
*/
|
||||||
public interface OnFragmentInteractionListener {
|
public interface OnFragmentInteractionListener {
|
||||||
void onFragmentStopped(Vector<Double> bpmList);
|
void onFragmentStopped();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,13 @@ public class AccelerometerData {
|
|||||||
this.z = z;
|
this.z = z;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AccelerometerData(AccelerometerData other){
|
||||||
|
this.ts = other.ts;
|
||||||
|
this.x = other.x;
|
||||||
|
this.y = other.y;
|
||||||
|
this.z = other.z;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object other){
|
public boolean equals(Object other){
|
||||||
|
|
||||||
|
|||||||
@@ -20,19 +20,20 @@ public class AccelerometerWindowBuffer extends ArrayList<AccelerometerData> {
|
|||||||
|
|
||||||
//TODO: add exception handling. falseArgument if ad has no numeric x,y,z
|
//TODO: add exception handling. falseArgument if ad has no numeric x,y,z
|
||||||
public boolean add(AccelerometerData ad){
|
public boolean add(AccelerometerData ad){
|
||||||
|
synchronized (this){
|
||||||
|
//do not add duplicates!
|
||||||
|
if (!isEmpty() && getYongest().equals(ad)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
//do not add duplicates!
|
boolean r = super.add(ad);
|
||||||
if(!isEmpty() && getYongest().equals(ad)){
|
if (size() > mWindowSize) {
|
||||||
return false;
|
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(){
|
public boolean isNextWindowReady(){
|
||||||
@@ -44,6 +45,16 @@ public class AccelerometerWindowBuffer extends ArrayList<AccelerometerData> {
|
|||||||
return false;
|
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() {
|
public AccelerometerData getYongest() {
|
||||||
return get(size() - 1);
|
return get(size() - 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,14 +44,15 @@ public class BpmEstimator {
|
|||||||
//mKalman = new SimpleKalman();
|
//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;
|
double sampleRate = mSampleRate_ms;
|
||||||
if(sampleRate <= 0){
|
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?
|
//are we conducting?
|
||||||
//just look at the newest 512 samples
|
//just look at the newest 512 samples
|
||||||
@@ -90,6 +91,8 @@ public class BpmEstimator {
|
|||||||
int resetAfter = (int) Math.round(mResetLimit_ms / (mBuffer.getOverlapSize() * sampleRate));
|
int resetAfter = (int) Math.round(mResetLimit_ms / (mBuffer.getOverlapSize() * sampleRate));
|
||||||
if(++mResetCounter > resetAfter){
|
if(++mResetCounter > resetAfter){
|
||||||
mBpmHistory.clear();
|
mBpmHistory.clear();
|
||||||
|
|
||||||
|
//TODO: send signal to clear.
|
||||||
mBuffer.clear();
|
mBuffer.clear();
|
||||||
mMvg.clear();
|
mMvg.clear();
|
||||||
mResetCounter = 0;
|
mResetCounter = 0;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package de.tonifetzer.conductorswatch.utilities;
|
package de.tonifetzer.conductorswatch.utilities;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -8,20 +10,23 @@ import java.io.IOException;
|
|||||||
* Created by toni on 20/12/17.
|
* Created by toni on 20/12/17.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class FileWriter {
|
public class ByteStreamWriter {
|
||||||
|
|
||||||
private String mFilename;
|
|
||||||
|
|
||||||
private ByteArrayOutputStream mByteStream;
|
private ByteArrayOutputStream mByteStream;
|
||||||
private DataOutputStream mDataStream;
|
private DataOutputStream mDataStream;
|
||||||
|
|
||||||
FileWriter(String filename){
|
private final long mFirstTimestamp;
|
||||||
mFilename = filename;
|
int mDataCounter;
|
||||||
|
|
||||||
|
public ByteStreamWriter(){
|
||||||
mByteStream = new ByteArrayOutputStream();
|
mByteStream = new ByteArrayOutputStream();
|
||||||
mDataStream = new DataOutputStream(mByteStream);
|
mDataStream = new DataOutputStream(mByteStream);
|
||||||
|
|
||||||
|
mDataCounter = 0;
|
||||||
|
mFirstTimestamp = System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeDouble(double data){
|
private void writeDouble(double data){
|
||||||
try {
|
try {
|
||||||
mDataStream.writeDouble(data);
|
mDataStream.writeDouble(data);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -29,15 +34,15 @@ public class FileWriter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeString(String data){
|
private void writeChar(char data){
|
||||||
try {
|
try {
|
||||||
mDataStream.writeChars(data);
|
mDataStream.writeChar(data);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeInt(int data){
|
private void writeInt(int data){
|
||||||
try {
|
try {
|
||||||
mDataStream.writeInt(data);
|
mDataStream.writeInt(data);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -45,12 +50,30 @@ public class FileWriter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getByteArray(){
|
private void writeLong(long data){
|
||||||
return mByteStream.toByteArray();
|
try {
|
||||||
|
mDataStream.writeLong(data);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFilename(){
|
public void writeSensor3D(int type, double x, double y, double z){
|
||||||
return mFilename;
|
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(){
|
public void reset(){
|
||||||
@@ -61,6 +84,8 @@ public class FileWriter {
|
|||||||
try {
|
try {
|
||||||
mByteStream.close();
|
mByteStream.close();
|
||||||
mDataStream.close();
|
mDataStream.close();
|
||||||
|
|
||||||
|
mDataCounter = 0;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:text="240"
|
android:text="80"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:textColor="#158b69"
|
android:textColor="#158b69"
|
||||||
android:textSize="40sp"/>
|
android:textSize="40sp"/>
|
||||||
|
|||||||
Reference in New Issue
Block a user