package com.tgnourse.aprs; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioTrack; import android.os.Handler; import android.util.Log; import android.widget.TextView; public class SensorDataRunnable implements Runnable { /* * This class prepares the sensors and builds the AX.25 audio clips and * plays them. Pressing the run button registers listeners for all of the * sensors that we are interested in. When all of the sensors have reported * in, we build a frame then pass it to the audio player. After we have a * copy of all the sensor data, we reset the "all present flag" then wait * for new data to arrive. If the run button is pressed again (stop) then we * unregister the sensor listeners which stops the whole process. The whole * read/process/play loop takes about one second in real time. */ // 22050 Sample Rate /* * public final static int sampleRate = 22050; public final static short[] * SAMP22 = { 0, 19222, 31134, 31206, 19410, 233, -19031, -31060, -31276, * -19598, -466, 18842, 30985, 31345, 19784, 700, -18650, -30908, -31412, * -19970 }; * * public final static short[] SAMP12 = { 0, 10987, 20702, 28020, 32093, * 32451, 29051, 22287, 12942, 2099, -8986, -19032, -26874, -31604, -32675, * -29962, -23780, -14844 }; */ // 44100 Sample rate public final static int sampleRate = 44100; /* * public final static short[] SAMP22 = { 0, 10103, 19222, 26467, 31134, * 32766, 31206, 26604, 19410, 10325, 233, -9881, -19032, -26329, -31060, * -32765, -31276, -26740, -19598, -10546, -466, 9658, 18842, 26189, 30985, * 32761, 31345, 26874, 19784, 10767, 700, -9435, -18650, -26049, -30908, * -32756, -31412, -27007, -19970, -10987 }; */ public final static short[] SAMP22 = { 0, 10103, 19222, 26467, 31134, 32766, 31206, 26604, 19410, 10325, 233, -9881, -19032, -26329, -31060, -32765, -31276, -26740, -19598, -10546, -466, 9658, 18842, 26189, 30985, 32761, 31345, 26874, 19784, 10767, 700, -9435, -18650, -26049, -30908, -32756 }; public final static short[] SAMP12 = { 0, 5574, 10987, 16079, 20702, 24721, 28020, 30501, 32093, 32750, 32451, 31206, 29051, 26049, 22287, 17875, 12942, 7632, 2099, -3494, -8986, -14217, -19032, -23293, -26874, -29672, -31604, -32615, -32675, -31782, -29962, -27269, -23780, -19598, -14844, -9658 }; private final static double fullCycle = 2 * Math.PI; private final static int baud = 1200; private final static double numberOfSamples = sampleRate / baud; // might need to be a double // private final SensorManager mSensorManager; // private final Sensor mAccel, mGyro, mMag, mTemp, mPress, mLight; private TextView frameTextView; private TextView timeStampTextView; private TextView aprsTextView; private boolean play; private Handler handler; private double phase; private SensorDataCollector data; public SensorDataRunnable(Handler handler, TextView frame, TextView timeStamp, TextView aprs, SensorDataCollector data) { this.handler = handler; this.frameTextView = frame; this.timeStampTextView = timeStamp; this.aprsTextView = aprs; this.data = data; } public void run() { /* * Setup sensor listeners mSensorManager = * (SensorManager)getSystemService(SENSOR_SERVICE); mAccel = * mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); mGyro = * mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE); mMag = * mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD); mTemp = * mSensorManager.getDefaultSensor(Sensor.TYPE_AMBIENT_TEMPERATURE); * mPress = mSensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE); * mLight = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); */ short[] sound; play = true; // set up audio player int minBufferSize = AudioTrack.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT); AudioTrack track = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, minBufferSize, AudioTrack.MODE_STREAM); track.play(); while (play) { String src = "KC9NZJ" + (char) 0xd; String dst = "100KFT" + (char) 0x3; final String info = makeInfoField(data.getTime(), data.getLocation(), data.getAccel(), data.getGyro(), data.getMag(), data.getTemp(), data.getPress(), data.getLight()); final String frame = makeAprsFrame(src, dst, info); sound = encodeBell202(frame, 30, 10); track.write(sound, 0, sound.length); handler.post(new Runnable() { public void run() { if (frameTextView != null) frameTextView.setText(frame); DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); Date date = new Date(); if (timeStampTextView != null) timeStampTextView.setText(dateFormat.format(date)); if (aprsTextView != null) aprsTextView.setText(info); } }); } track.stop(); track.flush(); track.release(); } public boolean playing() { return play; } public void stop() { play = false; } public String makeInfoField( // X = left(-) to right(+) across screen // Y = bottom(-) to top(+) across screen // Z = behind(-) to front(+) of screen long time, // timestamp in mS double[] gps, // lat, long, alt float[] accelerometer, // x, y, z (m/S^2) float[] gyroscope, // x, y, z (radians/S) float[] magnetometer, // x, y, z (uT) float[] temperature, // degree C float[] pressure, // hPa (millibar) float[] light // SI Lux ) { String oMsgType = "/"; String oTable = "/"; String oSymbol = "S"; String oTime = "083522h"; String oGpsLat = d2dm(gps[0], true); // "3725.32N"; String oGpsLon = d2dm(gps[1], false); // "12205.04W"; String oGpsAlt = "/A=" + ("000000" + String.valueOf(Math.floor(gps[2])) .substring(0, 6)); String oGps = oGpsLat + oTable + oGpsLon + oSymbol + oGpsAlt; String oAccelX = intToBase91((int) ((20 + accelerometer[0]) * 100), 2); String oAccelY = intToBase91((int) ((20 + accelerometer[1]) * 100), 2); String oAccelZ = intToBase91((int) ((20 + accelerometer[2]) * 100), 2); String oAccel = "A" + oAccelX + oAccelY + oAccelZ; String oGyroX = intToBase91((int) ((35 + gyroscope[0]) * 100), 2); String oGyroY = intToBase91((int) ((35 + gyroscope[1]) * 100), 2); String oGyroZ = intToBase91((int) ((35 + gyroscope[2]) * 100), 2); String oGyro = "G" + oGyroX + oGyroY + oGyroZ; String oMagX = intToBase91((int) ((3750 + magnetometer[0]) * 100), 3); String oMagY = intToBase91((int) ((3750 + magnetometer[1]) * 100), 3); String oMagZ = intToBase91((int) ((3750 + magnetometer[2]) * 100), 3); String oMag = "M" + oMagX + oMagY + oMagZ; String oTemp = intToBase91((int) ((274 + temperature[0]) * 10), 2); String oPress = intToBase91((int) (pressure[0] * 100), 3); String oIlum = intToBase91((int) light[0], 3); String oEnviro = "E" + oTemp + oPress + "I" + oIlum; return oMsgType + oTime + oGps + oAccel + oGyro + oMag + oEnviro; } public String makeAprsFrame(String source, String destination, String info) { char control = 0x03; char protocol = 0xf0; String src = makeAddress(source, true); String dst = makeAddress(destination); String frame = dst + src + control + protocol + info; String fcs = fcs(frame); return frame + fcs; } public String makeAddress(String text) { return makeAddress(text, false); } public String makeAddress(String text, boolean isFinal) { /* * The Address Field The address field is 7 bytes long. The first 6 * bytes are the call sign right padded with spaces if needed. The call * sign can only contain 0-9 and A-Z (not a-z). Only the lower 7 bits * are used (0-127), which gets left-shifted one. The LSB is used as a * "more data" bit where 0 means more data and 1 means stop. This * function will set this bit if isFinal is true. The SSID is a number * from 0-15 (4 bits) which is left-shifted four. The return value is 7 * characters long. */ char ssid = 0x0; text = text.toUpperCase(); String call = ""; // Is the last character an SSID of 0-15? if (text.charAt(text.length() - 1) <= 0xF) { ssid = text.charAt(text.length() - 1); Log.i("Rocket!", "Found SSID of " + (int) ssid + ":" + text); ssid = (char) ((ssid << 4) & 0xF0); if (isFinal) { ssid++; } } else { Log.i("Rocket!", "No SSID found:" + ":" + text); } // limit call sign to [0-9A-Z] for (int i = 0; i < text.length(); i++) { char thisChar = text.charAt(i); if (isAlphaNumeric(thisChar)) { // left-shift 1 all call sign characters call += (char) ((thisChar << 1) & 0xFF); } else { call += "@"; } } // pad/limit call sign to 6 characters by adding 0x32<<1 to end call = (call + "@@@@@@").substring(0, 6); Log.i("Rocket!", "Address built:(" + (call + ssid).length() + ")" + call + ssid); return call + ssid; } public short[] encodeBell202(String frame) { return encodeBell202(frame, 30, 10); } public short[] encodeBell202(String frame, int leadDelay, int endDelay) { // short[] output = new short[14600]; short[] output = new short[(frame.length() + leadDelay + endDelay + 1) * 8 * SAMP22.length]; // play clean tone /* * short[] thisSample = SAMP22; * * short[] output = new short[thisSample.length * 1000]; for (int i = 0; * i < output.length;) { for(short sample: thisSample) {output[i++] = * sample;} } if (frame != "") { return output; } */ boolean lastSamp12 = true; byte runningOnes = 0; int charIndex = 0; int outIndex = 0; // send lead flags // for each bit in nextChar // if 0, send different sample from last time // , update lastSamp12 // , runningOnes = 0; // if 1, send same sample as last time // , update lastSamp12 // , runningOnes++ // , if runningOnes = 5, send extra 0 // , runningOnes = 0 // send trailing flags // A flag is 0x7E or 0b01111110 for (int index = 0; index < leadDelay; index++) { for (short sample : getCycles(2200)) { output[outIndex++] = sample; } for (short sample : getCycles(2200)) { output[outIndex++] = sample; } for (short sample : getCycles(2200)) { output[outIndex++] = sample; } for (short sample : getCycles(2200)) { output[outIndex++] = sample; } for (short sample : getCycles(2200)) { output[outIndex++] = sample; } for (short sample : getCycles(2200)) { output[outIndex++] = sample; } for (short sample : getCycles(2200)) { output[outIndex++] = sample; } for (short sample : getCycles(1200)) { output[outIndex++] = sample; } } // String debug = ""; for (char nextChar : frame.toCharArray()) { // debug = "Processing " + nextChar + ":" + (int)nextChar + " "; for (charIndex = 0; charIndex < 8; charIndex++) { if ((nextChar & 0x1) == 0) { // for(short sample: getCycles(1200)) {output[outIndex++] = // sample;} // 0 means send different sample from last time if (lastSamp12) { // debug += "0/22 "; for (short sample : getCycles(2200)) { output[outIndex++] = sample; } lastSamp12 = false; } else { // debug += "0/12 "; for (short sample : getCycles(1200)) { output[outIndex++] = sample; } lastSamp12 = true; } runningOnes = 0; } else { // for(short sample: getCycles(2200)) {output[outIndex++] = // sample;} // 1 means send same sample as last time if (lastSamp12) { // debug += "1/12 "; for (short sample : getCycles(1200)) { output[outIndex++] = sample; } // lastSamp12 = true; } else { // debug += "1/22 "; for (short sample : getCycles(2200)) { output[outIndex++] = sample; } // lastSamp12 = false;; } runningOnes++; if (runningOnes == 5) { // debug += "*"; charIndex--; nextChar >>>= 1; nextChar <<= 1; } } nextChar >>>= 1; } // Log.i("encodeBell202",debug); } for (int index = 0; index < endDelay; index++) { if (lastSamp12) { for (short sample : getCycles(1200)) { output[outIndex++] = sample; } for (short sample : getCycles(2200)) { output[outIndex++] = sample; } for (short sample : getCycles(2200)) { output[outIndex++] = sample; } for (short sample : getCycles(2200)) { output[outIndex++] = sample; } for (short sample : getCycles(2200)) { output[outIndex++] = sample; } for (short sample : getCycles(2200)) { output[outIndex++] = sample; } for (short sample : getCycles(2200)) { output[outIndex++] = sample; } for (short sample : getCycles(1200)) { output[outIndex++] = sample; } } else { for (short sample : getCycles(2200)) { output[outIndex++] = sample; } for (short sample : getCycles(1200)) { output[outIndex++] = sample; } for (short sample : getCycles(1200)) { output[outIndex++] = sample; } for (short sample : getCycles(1200)) { output[outIndex++] = sample; } for (short sample : getCycles(1200)) { output[outIndex++] = sample; } for (short sample : getCycles(1200)) { output[outIndex++] = sample; } for (short sample : getCycles(1200)) { output[outIndex++] = sample; } for (short sample : getCycles(2200)) { output[outIndex++] = sample; } } } Log.i("FrameOutput", "Output length = " + outIndex); return output; } public String d2dm(double degrees, boolean isLat) { double minutes = 60 * (Math.abs(degrees) - Math.abs((int) degrees)); String degWhole = right("000" + Math.abs((int) degrees), 3); String minWhole = right("00" + (int) minutes, 2); String minDec = right("00" + (int) ((minutes - (int) minutes) * 100), 2); if (isLat) { degWhole = right(degWhole, 2); } String output = degWhole + minWhole + "." + minDec; if (isLat) { if (degrees >= 0) { output += "N"; } else { output += "S"; } } else { if (degrees >= 0) { output += "E"; } else { output += "W"; } } return output; } public boolean isAlphaNumeric(char character) { // is it a number? if (character >= 0x30 && character <= 0x39) { return true; } // is it a upper case letter if (character >= 0x41 && character <= 0x5A) { return true; } // is it a lower case letter if (character >= 0x61 && character <= 0x7A) { return true; } return false; } public String right(String text, int len) { return text.substring(text.length() - len, text.length()); } public String fcs(String frame) { return "XX"; } public String intToBase91(int value) { return intToBase91(value, 0); } public String intToBase91(int value, int length) { String output = ""; double power = 0; if (length < 1) { length = 1; while (value / java.lang.Math.pow(91, length) > 1) { length++; } } while (length > 0) { length--; power = java.lang.Math.pow(91, length); output += (char) (value / power + 33); value %= power; } return output; } public short[] getCycles(double frequency) { // double phase = the phase of the last sample (global) // int sampleRate = the audio sample rate (global) // double fullCycle = 2 * Math.PI(global) // int baud = baud rate (global) // double numberOfSamples = number of samples required at give sample // rate (global) // String debug = ""; double step = ((frequency / baud) * fullCycle) / numberOfSamples; // the phase step for the next value short[] output = new short[(int) numberOfSamples]; int outputIndex = 0; while (outputIndex < numberOfSamples) { output[outputIndex++] = (short) (Short.MAX_VALUE * Math.sin(phase)); // debug += ((short)(Short.MAX_VALUE * Math.sin(phase))) + ", "; phase += step; } // Log.i("getCycles","f:" + frequency + " s:" + step); return output; } }