close #26 - outputstream implementiert, welche kontinuirliche daten schreibt

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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