package org.spin.gaitlib.sensor; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.location.Location; import android.location.LocationListener; import android.os.Bundle; import android.os.Environment; import android.util.Log; import org.spin.gaitlib.GaitAnalysis; import org.spin.gaitlib.core.ILoggable; import org.spin.gaitlib.util.Logger; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.TimeUnit; /** * A listener that can listen for events from accelerometer, gyroscope and location service. It * caches recent sensor reading that are within a time period set by <code>windowSize</code>. */ public class SignalListener implements SensorEventListener, LocationListener, ILoggable { private static final String TAG = "SignalListener"; /** * Time at which {@link SignalListener} is created, measured in nanoseconds. */ private final long startTime; /** * Window size measured in milliseconds. The default value is set by {@link GaitAnalysis} class; * it can be changed on the fly by calling {@link #setWindowSize(int)}. */ private int windowSize = GaitAnalysis.DEFAULT_WINDOW_SIZE_MS; /** * A buffer for {@link LocationReading}'s. */ private final ArrayList<LocationReading> locs = new ArrayList<LocationReading>(); /** * A buffer for accelerometer readings. */ private final ArrayList<ThreeAxisSensorReading> accel = new ArrayList<ThreeAxisSensorReading>(); /** * A buffer for gyroscope readings. */ private final ArrayList<ThreeAxisSensorReading> gyro = new ArrayList<ThreeAxisSensorReading>(); private final Logger accelReadingLogger = new Logger(null, ".acsv", "TimeSinceStart(ms), x, y, z"); public SignalListener() { startTime = System.nanoTime(); } public void onSensorChanged(SensorEvent event) { switch (event.sensor.getType()) { case Sensor.TYPE_ACCELEROMETER: addAccelReading(createThreeAxisReading(event.values[0], event.values[1], event.values[2], event.timestamp, TimeUnit.NANOSECONDS)); break; case Sensor.TYPE_GYROSCOPE: addGyroReading(createThreeAxisReading(event.values[0], event.values[1], event.values[2], event.timestamp, TimeUnit.NANOSECONDS)); break; } } public void onAccuracyChanged(Sensor sensor, int accuracy) { } public void onLocationChanged(Location location) { addLocationReading(createLocationReading(location)); } public void onStatusChanged(String provider, int status, Bundle extras) { } public void onProviderEnabled(String provider) { } public void onProviderDisabled(String provider) { } private ThreeAxisSensorReading createThreeAxisReading(float x, float y, float z, long timestamp, TimeUnit timestampUnit) { return new ThreeAxisSensorReading(x, y, z, getTimeSinceStart(timestamp, timestampUnit), timestamp, timestampUnit); } private LocationReading createLocationReading(Location location) { long timestamp = location.getTime(); // TODO: Check for correctness TimeUnit timestampUnit = TimeUnit.MILLISECONDS; return new LocationReading(location, getTimeSinceStart(timestamp, timestampUnit), timestamp, timestampUnit); } /** * @param currentNanoTime * @return current time since signal listener is created, measured in nanoseconds. */ public long getTimeSinceStart(long currentTime, TimeUnit currentTimeUnit) { if (!TimeUnit.NANOSECONDS.equals(currentTimeUnit)) { currentTime = TimeUnit.NANOSECONDS.convert(currentTime, currentTimeUnit); } return currentTime - this.startTime; } private void addAccelReading(ThreeAxisSensorReading reading) { if (reading != null) { accel.add(reading); trimToWindowSize(accel, windowSize); accelReadingLogger.printRow(reading.getStringArray()); } } private void addGyroReading(ThreeAxisSensorReading reading) { if (reading != null) { gyro.add(reading); trimToWindowSize(gyro, windowSize); } } private void addLocationReading(LocationReading location) { locs.add(location); trimToWindowSize(locs, windowSize); } private void trimToWindowSize(List<? extends SignalReading> readings, int windowSizeMS) { long trimmedWindowStartTimestamp = readings.get(readings.size() - 1).getAbsoluteTime( TimeUnit.MILLISECONDS) - windowSizeMS; int endIndex = 0; for (SignalReading r : readings) { if (r.getAbsoluteTime(TimeUnit.MILLISECONDS) < trimmedWindowStartTimestamp) { endIndex++; } else { break; } } if (endIndex > 0) { // Remove elements that are outside the range we are looking at readings.subList(0, endIndex).clear(); } } public List<ThreeAxisSensorReading> getAccelReadings() { List<ThreeAxisSensorReading> accelCopy = new ArrayList<ThreeAxisSensorReading>( accel); removeNullElements(accelCopy); // sometimes the last few elements of the list could be null. return accelCopy; } private List<? extends SignalReading> removeNullElements(List<? extends SignalReading> list) { int lastIndex = list.size() - 1; if (list.get(lastIndex) == null) { list.remove(lastIndex); return removeNullElements(list); } return list; } public ThreeAxisSensorReading[] getAccelReadingsArray() { List<ThreeAxisSensorReading> accelCopy = getAccelReadings(); ThreeAxisSensorReading[] array = accelCopy.toArray(new ThreeAxisSensorReading[accelCopy .size()]); return array; } public ArrayList<ThreeAxisSensorReading> getGyroReadings() { return this.gyro; } public ArrayList<LocationReading> getLocations() { return this.locs; } /** * @return the time this <code>SignalListener</code> was created, measured in Unix time in * milliseconds. */ public long getStartTime() { return this.startTime; } /** * @return the window size for data used in gait and cadence analysis, measured in millisecond. */ public int getWindowSize() { return windowSize; } /** * @param windowSize the window size in millisecond. */ public void setWindowSize(int windowSize) { this.windowSize = windowSize; } public void updateLocation(Location loc) { locs.add(createLocationReading(loc)); } public void clearLogs() { accel.clear(); locs.clear(); } public void setLoggingEnabled(boolean enabled) { accelReadingLogger.setEnabled(enabled); } public boolean isLogging() { return accelReadingLogger.isEnabled(); } /* * Methods below are inherited from the original implementation of GaitLib. Logging is now * handled by org.spin.gaitlib.util.Logger. */ public void writeLocationsToFile() { this.writeDataFile(locs, "locs"); } public void writeAccelDataToFile() { this.writeDataFile(accel, "accel"); } public void writeFasperDataToFile(float[][] data) { ArrayList<FasperPoint> results = new ArrayList<FasperPoint>(); for (int i = 0; i < data.length; i++) { results.add(new FasperPoint(data[i])); } writeDataFile(results, "fasper"); } public void writeDataFile(@SuppressWarnings("rawtypes") ArrayList objs, String prefix) { writeDataFile(objs, prefix, null); } public void writeDataFile(@SuppressWarnings("rawtypes") ArrayList objs, String prefix, String[] header) { try { File root = new File(Environment.getExternalStorageDirectory(), prefix); root.mkdirs(); if (root.canWrite()) { DateFormat dateFormat = new SimpleDateFormat("yyyyMMMdd-HH-mm-ss"); Date date = new Date(); File gpxfile = new File(root, "log_" + dateFormat.format(date) + ".txt"); Log.v(TAG, "Writing: " + gpxfile.getName()); FileWriter gpxwriter = new FileWriter(gpxfile); BufferedWriter out = new BufferedWriter(gpxwriter); // write header first if (header != null) { for (String s : header) { out.write(s + "\n"); } } // now write data for (Object obj : objs) { out.write(obj + "\n"); } out.close(); Log.v(TAG, "Wrote to file:" + gpxfile.getAbsolutePath()); } else { Log.v(TAG, "Can't Write to File"); } } catch (IOException e) { Log.v("INFO", "Could not write file " + e.getMessage()); } } class FasperPoint { private final float freq; private final float power; public FasperPoint(float[] arr) { freq = arr[0]; power = arr[1]; } @Override public String toString() { return freq + "," + power; } } }