// Copyright 2012 (C) Matthew Brejza // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. package com.brejza.matt.habmodem; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import org.mapsforge.core.GeoPoint; import com.brejza.matt.habmodem.PredictionGrabber.PredictionRxEvent; import rtty.StringRxEvent; import rtty.fsk_receiver; import ukhas.AscentRate; import ukhas.Gps_coordinate; import ukhas.HabitatRxEvent; import ukhas.Habitat_interface; import ukhas.Listener; import com.brejza.matt.habmodem.Payload; import ukhas.TelemetryConfig; import ukhas.Telemetry_string; import android.app.NotificationManager; import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.Color; import android.location.Criteria; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioRecord; import android.media.AudioTrack; import android.media.MediaRecorder.AudioSource; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.preference.PreferenceManager; import android.support.v4.app.NotificationCompat; import android.util.Log; import android.widget.Toast; public class Dsp_service extends Service implements StringRxEvent, HabitatRxEvent, PredictionRxEvent { public final static String TELEM_RX = "com.brejza.matt.habmodem.TELEM_RX"; public final static String CHAR_RX = "com.brejza.matt.habmodem.CHAR_RX"; public final static String CHARS = "com.brejza.matt.habmodem.CHARS"; public final static String FFT_UPDATED = "com.brejza.matt.habmodem.FFT_UPDATED"; public final static String HABITAT_NEW_DATA = "com.brejza.matt.habmodem.HABITAT_NEW_DATA"; public final static String PREDICTION_NEW_DATA = "com.brejza.matt.habmodem.PREDICTION_NEW_DATA"; public final static String TELEM_STR = "com.brejza.matt.habmodem.TELEM_STR"; public final static String GPS_UPDATED = "com.brejza.matt.habmodem.GPS_UPDATED"; public final static String LOG_EVENT = "com.brejza.matt.habmodem.LOG_EVENT"; public final static String LOG_STR = "com.brejza.matt.habmodem.LOG_STR"; // Binder given to clients private final IBinder mBinder = new LocalBinder(); fsk_receiver rcv = new fsk_receiver(); private AudioRecord mRecorder; private AudioTrack mPlayer; private int buffsize; boolean isRecording = false; boolean usingMic = false; HeadsetReceiver headsetReceiver; private int _baud = 300; private int last_colour = 0; Telemetry_string last_str; private boolean enable_bt = false; boolean _enableChase = false; boolean _enablePosition = false; private boolean enableEcho = false; public boolean enableBell = false; public boolean enableUploader = true; private boolean _enableDecoder = false; Timer updateTimer; Timer serviceInactiveTimer; private int lastHabitatFreq=0; NotificationManager nm; Handler handler; Handler handler2; Toast toast; public double currentLatitude = 0; public double currentLongitude = 0; public boolean currentLocationValid = false; private Location_handler loc_han; private LocationManager locationManager; //private long chasecarUpdateSecs = 45; private long lastChasecarUpdate = 0; //moving_average ascent_rates; LoggingQueue log = new LoggingQueue(200); Habitat_interface hab_con; PredictionGrabber pred_grab; private boolean listRxStrUpdated = false; private boolean dataReloaded = false; // //save this data on close // public List<String> listRxStr = Collections.synchronizedList(new ArrayList<String>()); private ConcurrentHashMap<String, Payload> mapPayloads = new ConcurrentHashMap<String, Payload>(); // // // public Dsp_service() { rcv.addStringRecievedListener(this); } @Override public void onCreate() { super.onCreate(); System.out.println("Service started"); } @Override public IBinder onBind(Intent arg0) { if (!isRecording) serviceRestart(); if (serviceInactiveTimer != null){ serviceInactiveTimer.cancel(); serviceInactiveTimer = null; logEvent("Stopping Inactivity Timer",false); } if (Build.VERSION.SDK_INT >= 18){ final IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(BluetoothLeService.BT_TELEM); registerReceiver(mGattUpdateReceiver, intentFilter); } System.out.println("DEBUG : something bound"); handler = new Handler(); handler2 = new Handler(); //string receiver if (headsetReceiver == null) headsetReceiver = new HeadsetReceiver(); IntentFilter intentFilter1 = new IntentFilter(Intent.ACTION_HEADSET_PLUG); registerReceiver(headsetReceiver, intentFilter1); if (pred_grab == null) { pred_grab = new PredictionGrabber(this.getApplicationContext(), PreferenceManager.getDefaultSharedPreferences(this).getString("pref_predictor_server", "spacenear.us/tracker/get_predictions.php")); pred_grab.addPredictorUpdateListener(this); } if (hab_con == null){ String call_u = PreferenceManager.getDefaultSharedPreferences(this.getApplicationContext()).getString("pref_callsign", "USER"); hab_con = new Habitat_interface( PreferenceManager.getDefaultSharedPreferences(this).getString("pref_habitat_server", "habitat.habhub.org"), PreferenceManager.getDefaultSharedPreferences(this).getString("pref_habitat_db", "habitat"), new Listener(call_u, new Gps_coordinate(50.2,-0.6,0),false)); //hab_con.upload_payload_telem(new Telemetry_string("$$ASTRA,12:12:12,5044.11111,-001.00000,1212,34*1234")); hab_con.addGetActiveFlightsTask(); hab_con.addHabitatRecievedListener(this); } if (loc_han == null){ loc_han = new Location_handler(); this.locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); } hab_con.device = android.os.Build.BRAND + " " + android.os.Build.MODEL; hab_con.device_software = android.os.Build.VERSION.RELEASE; hab_con.application = "HAB Modem for Android"; String vers = "unknown"; try{ Context cn = getApplicationContext(); vers = cn.getPackageManager().getPackageInfo(cn.getPackageName(), 0).versionName; } catch (NameNotFoundException e){ System.out.println("Cannot get version number - " + e.toString()); } hab_con.application_version = vers; if (!dataReloaded){ dataReloaded = true; loadState(); } //System.out.println("Starting audio"); return mBinder; } @Override public void onRebind(Intent intent) { if (!isRecording) serviceRestart(); if (serviceInactiveTimer != null){ serviceInactiveTimer.cancel(); serviceInactiveTimer = null; logEvent("Stopping Inactivity Timer",false); } System.out.println("REBOUND"); } @Override public boolean onUnbind(Intent intent) { System.out.println("DEBUG : something unbound"); startInactiveTimer(); return true; } @Override public void onDestroy() { if (Build.VERSION.SDK_INT >= 18) unregisterReceiver(mGattUpdateReceiver); if (headsetReceiver != null) unregisterReceiver(headsetReceiver); disableEcho(); if (mRecorder != null) { mRecorder.stop(); mRecorder.release(); } if (mPlayer != null) { mPlayer.stop(); mPlayer.release(); } nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); nm.cancel(0); System.out.println("Destroying service"); super.onDestroy(); } public class LocalBinder extends Binder { Dsp_service getService() { // Return this instance of LocalService so clients can call public methods return Dsp_service.this; } } public void changeLocationSettings(boolean enablePos, boolean enableChase) { if (!_enablePosition && !_enableChase && (enablePos || enableChase)) enableLocation(); if ((_enablePosition || _enableChase) && !enablePos && !enableChase) disableLocation(); _enablePosition = enablePos; _enableChase = enableChase; //Intent intent = new Intent(this, Map_Activity.class); //PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0); //icon at the bottom if (_enableChase) { } else { nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); nm.cancel(0); } } //doesnt need to be called when service first starts (but can be) public void serviceRestart() { startAudio(); if (countActivePayloads() > 0){ startUpdateTimer(); updateActivePayloadsHabitat(); } logEvent("Service Restarted",false); if (_enablePosition || _enableChase) enableLocation(); } public void servicePause() { isRecording = false; if (updateTimer != null){ updateTimer.cancel(); updateTimer = null; } logEvent("Service Paused",false); disableLocation(); } public int countActivePayloads() { int count=0; for (Map.Entry<String, Payload> entry : mapPayloads.entrySet()) { if (entry.getValue().isActivePayload()) count++; } return count; } private void enableLocation() { //my location part Criteria criteria = new Criteria(); criteria.setAccuracy(Criteria.ACCURACY_FINE); String bestProvider = this.locationManager.getBestProvider(criteria, true); if (bestProvider == null) return; System.out.println("STARTING GPS WITH: "+bestProvider); logEvent("Starting Location with: " + bestProvider,true); this.locationManager.requestLocationUpdates(bestProvider, 2000, 0, this.loc_han); } private void disableLocation() { if (locationManager != null) { locationManager.removeUpdates(this.loc_han); System.out.println("Disabling location"); } } public void startAudio() { if (!_enableDecoder) return; boolean mic = this.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE); System.out.println("isRecording: " + isRecording); logEvent("Starting Audio. Mic avaliable: " + mic,false); if (!isRecording) { isRecording = true; buffsize = AudioRecord.getMinBufferSize(8000,AudioFormat.CHANNEL_IN_MONO ,AudioFormat.ENCODING_PCM_16BIT); buffsize = Math.max(buffsize, 3000); mRecorder = new AudioRecord(AudioSource.MIC,8000, AudioFormat.CHANNEL_IN_MONO , AudioFormat.ENCODING_PCM_16BIT,buffsize); mPlayer = new AudioTrack(AudioManager.STREAM_MUSIC,8000,AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT,2*buffsize,AudioTrack.MODE_STREAM); if (enableEcho) { AudioManager manager = (AudioManager)getSystemService(Context.AUDIO_SERVICE); manager.setMode(AudioManager.MODE_IN_CALL); manager.setSpeakerphoneOn(true); } if (mRecorder.getState() != AudioRecord.STATE_INITIALIZED) { mRecorder = new AudioRecord(AudioSource.DEFAULT,8000, AudioFormat.CHANNEL_IN_MONO , AudioFormat.ENCODING_PCM_16BIT,buffsize); if (mRecorder.getState() != AudioRecord.STATE_INITIALIZED) { logEvent("Error - Could not initialise audio",true); return; } logEvent("Using default audio source",false); } mRecorder.startRecording(); System.out.println("STARTING THREAD"); Thread ct = new captureThread(); logEvent("Starting Audio Thread.",false); setDecoderRunningNotification(); ct.start(); } } // public void stopAudio() //{ // isRecording = false; //} private void updateTimerPeriod() { int interval = 3 * 60 * 1000; String inter = PreferenceManager.getDefaultSharedPreferences(this.getApplicationContext()).getString("pref_habitat_update_freq", "3"); try { interval = Integer.parseInt(inter) * 60 * 1000; if (interval != lastHabitatFreq && updateTimer != null){ updateTimer.cancel(); updateTimer.purge(); updateTimer = null; startUpdateTimer(); } } catch (Exception e) { } } private void startUpdateTimer() { if (updateTimer == null){ logEvent("Starting Update Timer",false); updateTimer = new Timer(); int interval = 3 * 60 * 1000; String inter = PreferenceManager.getDefaultSharedPreferences(this.getApplicationContext()).getString("pref_habitat_update_freq", "3"); try { interval = Integer.parseInt(inter) * 60 * 1000; } catch (Exception e) { interval = 3 * 60 * 1000; } if (interval < 30*1000) interval = 30*1000; lastHabitatFreq = interval; updateTimer.scheduleAtFixedRate(new UpdateTimerTask(), interval,interval); } } private void startInactiveTimer() { if (serviceInactiveTimer != null) serviceInactiveTimer.cancel(); serviceInactiveTimer = new Timer(); int interval = 20 * 60 * 1000; String sin = PreferenceManager.getDefaultSharedPreferences(this.getApplicationContext()).getString("pref_inactive", "20"); try { interval = Integer.parseInt(sin) * 60 * 1000; } catch (Exception e) { interval = 20 * 60 * 1000; } if (interval < 30*1000) { interval = 20 * 60 * 1000; } logEvent("Starting Inactivity Timer for " + interval/(60*1000) + " Minutes",false); serviceInactiveTimer.schedule(new InactiveTimerTask(), interval); } class UpdateTimerTask extends TimerTask { public void run() { updateActivePayloadsHabitat(); logEvent("Starting Habitat Refresh",true); updateTimerPeriod(); } } class InactiveTimerTask extends TimerTask { public void run() { servicePause(); } } public Telemetry_string getLastString() { return last_str; } public boolean payloadExists(String callsign) { return (mapPayloads.containsKey(callsign.toUpperCase(Locale.US))); } public boolean activePayloadExists(String callsign) { if (mapPayloads.containsKey(callsign.toUpperCase(Locale.US))){ return mapPayloads.get(callsign.toUpperCase(Locale.US)).isActivePayload(); } else return false; } public TreeMap<Long, Telemetry_string> getPayloadData (String callsign){ if (payloadExists(callsign)) return mapPayloads.get(callsign.toUpperCase(Locale.US)).data; else return null; } public List<GeoPoint> getPredictedPath (String callsign){ if (payloadExists(callsign)) return mapPayloads.get(callsign.toUpperCase(Locale.US)).predictedPath; else return null; } public double getAscentRate(String callsign) { if (payloadExists(callsign)) return mapPayloads.get(callsign.toUpperCase(Locale.US)).getAscentRate(); else return 0; } public double getMaxAltitude(String callsign){ if (payloadExists(callsign)) return mapPayloads.get(callsign.toUpperCase(Locale.US)).getMaxAltitude(); else return 0; } //public long getLastUpsddate(String callsign){ // if (payloadExists(callsign)) // return mapPayloads.get(callsign.toUpperCase(Locale.US)).getLastUpdated(); // else // return 0; //} //TODO: if already there, update infos /* public void addActivePayload(String call){ String callu = call.toUpperCase(Locale.US); if (!payloadExists(callu)) mapPayloads.put(callu,new Payload(call,true, 3)); else { Payload p = mapPayloads.get(callu); p.setIsActivePayload(true); } } */ public void addActivePayload(String call, int lookBehind){ startUpdateTimer(); String callu = call.toUpperCase(Locale.US); if (!payloadExists(callu)) { Payload p = new Payload(call,newColour(),true, lookBehind); mapPayloads.put(callu,p); } else { Payload p = mapPayloads.get(callu); p.setIsActivePayload(true); if (p.colour == 0) p.setNewColour(newColour()); p.setMaxLookBehindSecs(lookBehind); } } public void removeActivePayload(String call){ mapPayloads.get(call.toUpperCase(Locale.US)).clearUserData(); } public double[] getFFT() { return rcv.get_fft(); } public double getFFT(int i) { return rcv.get_fft(i); } public int get_f1_FFTbin() { return (int) (rcv.get_f1()*(rcv.FFT_half_len*2)); } public int get_f2_FFTbin() { return (int) (rcv.get_f2()*(rcv.FFT_half_len*2)); } public List<String> getActivePayloadList() { List<String> out = new ArrayList<String>(); for (Map.Entry<String, Payload> entry : mapPayloads.entrySet()) { if (entry.getValue().isActivePayload()) out.add(entry.getValue().callsign); } return out; } public ConcurrentHashMap<String,Payload> getPayloadList() { //check for any new payload data in habitat interface for (Map.Entry<String, String> entry : hab_con.payload_configs.entrySet()) { String call = entry.getKey().toUpperCase(Locale.US); if (payloadExists(call)) { //update records mapPayloads.get(call).setPayloadID(entry.getValue()); if (hab_con.flight_configs.containsKey(call)) mapPayloads.get(call).setFlightID(hab_con.flight_configs.get(call)); } else { //these payloads are not active if (hab_con.flight_configs.containsKey(entry.getKey().toUpperCase(Locale.US))) mapPayloads.put(call, new Payload(entry.getKey(),hab_con.payload_configs.get(call),hab_con.flight_configs.get(call))); else mapPayloads.put(call, new Payload(entry.getKey(),hab_con.payload_configs.get(call))); } } // out.add(entry.getKey()); return mapPayloads; } public TelemetryConfig getTelemetryConfig(String call) { if (payloadExists(call)) { return mapPayloads.get(call.toUpperCase(Locale.US)).telemetryConfig; } else return null; } public void enableEcho() { if (isRecording){ AudioManager manager = (AudioManager)getSystemService(Context.AUDIO_SERVICE); manager.setMode(AudioManager.MODE_IN_CALL); manager.setSpeakerphoneOn(true); } enableEcho = true; } public void disableEcho() { AudioManager manager = (AudioManager)getSystemService(Context.AUDIO_SERVICE); manager.setMode(AudioManager.MODE_NORMAL); manager.setSpeakerphoneOn(false); enableEcho = false; } class captureThread extends Thread { public void run() { android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO); short[] buffer = new short[buffsize]; double[] s = new double[buffsize]; mRecorder.startRecording(); isRecording = true; //AudioManager manager = (AudioManager)getSystemService(Context.AUDIO_SERVICE); boolean lastHead = false; int clippingCount = 0; int samplesSinceToast = 0; logEvent("Starting Audio. Buffer Size: " + buffsize,true); // setVolumeControlStream(AudioManager.STREAM_VOICE_CALL); //buffsize = mRecorder.read(buffer, 0, buffsize); //mPlayer.write(buffer, 0, buffsize); int readres; while(isRecording && _enableDecoder) { readres = mRecorder.read(buffer, 0, buffsize); if (readres < 10) { _enableDecoder = false; logEvent("Failed to get audio data, code " + readres,true); } else { if (usingMic && enableEcho){ mPlayer.write(buffer, 0, readres); if (mPlayer.getPlayState() != AudioTrack.PLAYSTATE_PLAYING && lastHead==true) mPlayer.play(); lastHead = true; } else{ if (mPlayer.getPlayState() == AudioTrack.PLAYSTATE_PLAYING){ mPlayer.stop(); mPlayer.flush(); } lastHead = false; } if (readres >= 512) { int i; s = new double [readres]; for (i = 0; i < readres; i++) s[i] = (double) buffer[i]; i=0; clippingCount = 0; while (i < readres) { if (buffer[i] > 30000 || buffer[i] < -30000) clippingCount++; i += 10; } if (clippingCount > 10){ if (samplesSinceToast <= 0 || samplesSinceToast > 8000*3) { samplesSinceToast = readres; System.out.println("Clipping detected"); handler.post(new Runnable(){ @Override public void run() { if (toast != null){ toast.cancel(); toast = null; } toast = Toast.makeText(getApplicationContext(), "Clipping Detected", Toast.LENGTH_SHORT); toast.show(); } }); } } samplesSinceToast += readres; String rxchar = rcv.processBlock(s,_baud); Intent it = new Intent(CHAR_RX); it.putExtra(CHARS, rxchar); sendBroadcast(it); if (listRxStrUpdated) sendBroadcast(new Intent(TELEM_RX)); if (rcv.get_fft_updated()) sendBroadcast(new Intent(FFT_UPDATED)); } } } if (mPlayer != null) { if(mPlayer.getState() != AudioTrack.STATE_UNINITIALIZED){ mPlayer.stop(); mPlayer.release(); } } if (mRecorder != null) { if (mRecorder.getState() != AudioRecord.STATE_UNINITIALIZED){ mRecorder.stop(); mRecorder.release(); } } System.out.println("DONE RECORDING"); logEvent("Stopping Audio",true); isRecording = false; mRecorder = null; mPlayer = null; AudioManager manager = (AudioManager)getSystemService(Context.AUDIO_SERVICE); manager.setMode(AudioManager.MODE_NORMAL); manager.setSpeakerphoneOn(false); nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); nm.cancel(1); } } public void enableDecoder() { _enableDecoder = true; startAudio(); } public void disableDecoder() { _enableDecoder = false; } public boolean getDecoderRunning() { return _enableDecoder; } public void updateActivePayloadsHabitat() { int count=0; int maxRec = 3000; String smr = PreferenceManager.getDefaultSharedPreferences(this.getApplicationContext()).getString("pref_habitat_max_lines", "3000"); try { maxRec = Integer.parseInt(smr); } catch (Exception e) { maxRec = 3000; } if (maxRec < 10 || maxRec > 99999999) { maxRec = 3000; } ConnectivityManager connMgr = (ConnectivityManager)this.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); if (networkInfo != null && networkInfo.isConnected()) { // fetch data for (Map.Entry<String, Payload> entry : mapPayloads.entrySet()) { if (entry.getValue().isActivePayload()){ count++; long start = entry.getValue().getUpdateStart(false); if ( start + 15 < (System.currentTimeMillis() / 1000L) ) { if (entry.getValue().getQueryOngoing() == 0 || entry.getValue().getQueryOngoing() < System.currentTimeMillis()-(60*1000)){ hab_con.addDataFetchTask(entry.getValue().callsign,start, (System.currentTimeMillis() / 1000L), maxRec);//entry.getValue().getMaxRecords()); entry.getValue().setQueryOngoing(System.currentTimeMillis()); } } } } } else { // display error logEvent("No network connection", true); } if (count < 1){ if (updateTimer != null){ updateTimer.cancel(); logEvent("Cancelling habitat update timer - no active payloads", false); } } else { handler2.post(new Runnable(){ @Override public void run() { pred_grab.getPredictions(); } }); } } private void InsertString(Telemetry_string str, boolean checksum) { String call = str.callsign.toUpperCase(Locale.US); if (call.equals("")) return ; if (!checksum && !mapPayloads.containsKey(call)) return; if (last_str == null) last_str = new Telemetry_string("",null); if (!str.raw_64_str().equals(last_str.raw_64_str())) { last_str = str; System.out.println("adding to list: " + str.getSentence().trim()); String st = str.getSentence().trim(); ///handler2.post(new Runnable(){ // @Override // public void run() { // listRxStr.add(st); // sendBroadcast(new Intent(TELEM_RX)); // } //}); listRxStr.add(st); listRxStrUpdated = true; logEvent("Decoded String - " + str.getSentence().trim(),true); if (checksum){ if (enableUploader) hab_con.upload_payload_telem(str); //upload received string to server if (serviceInactiveTimer != null){ startInactiveTimer(); } if (mapPayloads.containsKey(call)){ mapPayloads.get(call).setIsActivePayload(true); if (mapPayloads.get(call).colour == 0) mapPayloads.get(call).setNewColour(newColour()); mapPayloads.get(call).putPacket(str); if ((System.currentTimeMillis() / 1000L) -60 < mapPayloads.get(call).getLastUpdated()) mapPayloads.get(call).setLastUpdatedNow(); //if there are no (big) gaps since last string add current time as last update } else { //first one, dont need to do anything special //TreeMap<Long,Telemetry_string> l = new TreeMap<Long,Telemetry_string>(); //l.put(Long.valueOf(str.time.getTime()),str); //listPayloadData.put(str.callsign.toUpperCase(Locale.US),l); mapPayloads.put(call,new Payload(str,newColour())); startUpdateTimer(); updateActivePayloadsHabitat(); } if (str.coords != null){ if (str.coords.alt_valid) mapPayloads.get(call).putMaxAltitude(str.coords.altitude); } } else if (str.getSentence().length() > 10 && !payloadExists(str.callsign)){ mapPayloads.put(call,new Payload(call,newColour(),true)); } //sendBroadcast(new Intent(TELEM_RX)); } } @Override public void StringRx(byte[] strrx, boolean checksum, int length, int flags, int fixed) { Telemetry_string str = new Telemetry_string(strrx,null); str.habitat_metadata = new HashMap<String,String>(); str.habitat_metadata.put("receiver_flags", Integer.toHexString(flags)); str.habitat_metadata.put("fec_fixed", Integer.toString(fixed)); //TelemetryConfig tc = getTelemetryConfig(str.callsign); //if (tc != null) // str = new Telemetry_string(strrx,tc); InsertString(str, checksum); } public void StringRx(String str_, boolean checksum) { Telemetry_string str = new Telemetry_string(str_,null); TelemetryConfig tc = getTelemetryConfig(str.callsign); if (tc != null) str = new Telemetry_string(str_,tc); InsertString(str, checksum); } @Override public void HabitatRx(TreeMap<Long,Telemetry_string> data, boolean success, String callsign, long startTime, long endTime, AscentRate as, double maxAltitude) { String call = callsign.toUpperCase(Locale.US); if (mapPayloads.containsKey(call)) mapPayloads.get(call).setQueryOngoing(0); if (success) { System.out.println("DEBUG: Got " + data.size() + " sentences for payload " + callsign); logEvent("Habitat Query Got " + data.size() + " Sentences For Payload " + callsign,true); if (mapPayloads.containsKey(call)){ Payload p = mapPayloads.get(call); //if havnt already got a telem_config, see if one exists in hab_con if (p.telemetryConfig == null){ if (hab_con.getTelemConfigs().containsKey(call)){ p.telemetryConfig = hab_con.getTelemConfigs().get(call); } } long lt = p.getLastTime(); p.setLastUpdated(endTime); p.putPackets(data); p.setIsActivePayload(true); if (p.colour == 0) p.setNewColour(newColour()); if (data.size() > 0){ if (lt < Long.valueOf(data.lastKey())){ if (as != null){ if (as.valid()) p.ascentRate = as; } } } } else { Payload p = new Payload(callsign,newColour(),true); if (hab_con.getTelemConfigs().containsKey(call)){ p.telemetryConfig = hab_con.getTelemConfigs().get(call); } p.setLastUpdated(endTime); p.data = data; mapPayloads.put(call, p); if (as != null){ if (as.valid()) mapPayloads.get(call).ascentRate = as; } } mapPayloads.get(call).putMaxAltitude(maxAltitude); Intent i = new Intent(HABITAT_NEW_DATA); if (data.size() > 0) i.putExtra(TELEM_STR, data.get(data.lastKey()).getSentence()); sendBroadcast(i); } else { logEvent("Habitat Query Failed - " + callsign,true); } } public void logEvent(String event,boolean broadcast) { SimpleDateFormat sdfDate = new SimpleDateFormat("HH:mm:ss",Locale.US);//dd/MM/yyyy String s = sdfDate.format(new Date()) + " - " + event; if (broadcast){ Intent i = new Intent(LOG_EVENT); i.putExtra(LOG_STR, log.offerAndReturn(event)); sendBroadcast(i); } else log.offer(s); } public String getFromSettingsCallsign() { return PreferenceManager.getDefaultSharedPreferences(this.getApplicationContext()).getString("pref_callsign", "USER"); } private class HeadsetReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { usingMic = false; if (intent.getAction().equals(Intent.ACTION_HEADSET_PLUG)) { //Do stuff if (intent.hasExtra("microphone")) { if (intent.getIntExtra("microphone", 0) == 1){ logEvent("Using Line In",true); usingMic = true; return; } else{ logEvent("Not Using Line In",true); usingMic = false; } } else { logEvent("Headset: Not Using Line In",true); usingMic = false; } } } } public void setChaseCarNotification() { String body = "Uploading Chase Car Positions"; String title = "Chase Car"; NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_stat_car) .setContentTitle(title) .setAutoCancel(true) .setContentText(body); // Creates an explicit intent for an Activity in your app /* Intent resultIntent = new Intent(this, Map_Activity.class); TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); // Adds the back stack for the Intent (but not the Intent itself) stackBuilder.addParentStack(Map_Activity.class); // Adds the Intent that starts the Activity to the top of the stack stackBuilder.addNextIntent(resultIntent); PendingIntent resultPendingIntent = stackBuilder.getPendingIntent( 0, PendingIntent.FLAG_UPDATE_CURRENT ); mBuilder.setContentIntent(resultPendingIntent); */ NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); // mId allows you to update the notification later on. mNotificationManager.notify(0, mBuilder.build()); } public void setDecoderRunningNotification() { String body = "RTTY decoder is processing audio"; String title = "Decoder Running"; NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_stat_decoderrunning) .setContentTitle(title) .setContentText(body); NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); // mId allows you to update the notification later on. mNotificationManager.notify(1, mBuilder.build()); } private int newColour() { if (last_colour == 0) { last_colour = 0xFFFF0000; return last_colour; } else { float lasthsv[]= new float[3]; Color.colorToHSV(last_colour,lasthsv); lasthsv[0] = (lasthsv[0] + (180 + 33)) % 360; last_colour = Color.HSVToColor(lasthsv); return last_colour; } } private int getChaseCarUpdatePeriod() { int t = 45; String st = PreferenceManager.getDefaultSharedPreferences(this.getApplicationContext()).getString("pref_chase_update_freq", "45"); try { t=Integer.parseInt(st); } catch (Exception e) { t = 45; } if (t < 10 || t > 60*60*24) t = 45; return t; } public class Location_handler implements LocationListener { public Location_handler() { } @Override public void onLocationChanged(Location location) { int chasecarUpdateSecs = getChaseCarUpdatePeriod(); if ((lastChasecarUpdate + chasecarUpdateSecs < System.currentTimeMillis() / 1000L) && _enableChase){ String call_u = getFromSettingsCallsign(); float speed = location.getSpeed(); hab_con.updateChaseCar(new Listener(call_u, new Gps_coordinate(location.getLatitude(), location.getLongitude(),location.getAltitude()),speed,true)); lastChasecarUpdate = System.currentTimeMillis() / 1000L; setChaseCarNotification(); } currentLatitude = location.getLatitude(); currentLongitude = location.getLongitude(); currentLocationValid = true; sendBroadcast(new Intent(GPS_UPDATED)); } @Override public void onProviderDisabled(String provider) { nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); nm.cancel(0); } @Override public void onProviderEnabled(String provider) { } @Override public void onStatusChanged(String provider, int status, Bundle extras) { } } public boolean getEnableChase(){ return _enableChase; } public boolean getEnablePosition(){ return _enablePosition; } public boolean getEchoEnabled() { return enableEcho; } public Telemetry_string getMostRecent(String callsign) { if (mapPayloads.containsKey(callsign.toUpperCase(Locale.US))) { return mapPayloads.get(callsign.toUpperCase(Locale.US)).getLastString(); } else return null; } public LoggingQueue getLog(){ return log; } public TreeMap<Long,Telemetry_string> getAllData(String callsign) { if (mapPayloads.containsKey(callsign.toUpperCase(Locale.US))) { return mapPayloads.get(callsign.toUpperCase(Locale.US)).data; } else return null; } public void setBaud(int baud) { _baud = baud; } public int getBaud(){ return _baud; } public int getPayloadColour(String call) { call = call.toUpperCase(Locale.US); if (payloadExists(call)) { int i = mapPayloads.get(call).colour; if (i == 0) i = 0xFF000000; return i; } return 0; } @Override public void PredictionRx(HashMap<String,List<GeoPoint>> data) { if (data == null) return ; if (data.size() < 1) return; for (Map.Entry<String, List<GeoPoint>> entry : data.entrySet()) { String call = entry.getKey().toUpperCase(Locale.US); List<GeoPoint> value = entry.getValue(); if (payloadExists(call) && !call.equals("")) { Payload p = mapPayloads.get(call); p.predictedPath = value; p.lastPredictionGetTime = System.currentTimeMillis(); if (p.data != null){ if (p.data.lastEntry()!=null){ p.lastPredictionLocation = p.data.lastEntry().getValue().coords; } } } } Intent i = new Intent(PREDICTION_NEW_DATA); sendBroadcast(i); } private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Log.i("dsp_service, bt reciever","SERVICE GOT TELEM: " + intent.getStringExtra(BluetoothLeService.EXTRA_DATA)); String s = intent.getStringExtra(BluetoothLeService.EXTRA_DATA); byte[] b = intent.getByteArrayExtra(BluetoothLeService.EXTRA_DATA); if (s != null){ StringRx(s, true);//assume checksum is correct sendBroadcast(new Intent(TELEM_RX)); } else if (b.length > 0){ StringRx(b, true, b.length, 0,0); sendBroadcast(new Intent(TELEM_RX)); } } }; public void saveState() { String filename1 = "payload_telem.dat"; String filename2 = "payload_strings.dat"; FileOutputStream outputStream; try { outputStream = openFileOutput(filename1, Context.MODE_PRIVATE); ObjectOutputStream out = new ObjectOutputStream(outputStream); out.writeObject(mapPayloads); out.close(); outputStream.close(); } catch (Exception e) { e.printStackTrace(); } try { outputStream = openFileOutput(filename2, Context.MODE_PRIVATE); ObjectOutputStream out = new ObjectOutputStream(outputStream); out.writeObject(listRxStr); out.close(); outputStream.close(); } catch (Exception e) { e.printStackTrace(); } } @SuppressWarnings("unchecked") private void loadState() { String filename1 = "payload_telem.dat"; String filename2 = "payload_strings.dat"; try { File file = new File(this.getFilesDir(), filename1); if (System.currentTimeMillis() - file.lastModified() < (24*60*60*1000)){ //only load previous state if <1day old FileInputStream fileIn = openFileInput(filename1); ObjectInputStream in = new ObjectInputStream(fileIn); mapPayloads = (ConcurrentHashMap<String, Payload>) in.readObject(); in.close(); fileIn.close(); fileIn = openFileInput(filename2); in = new ObjectInputStream(fileIn); listRxStr = (List<String>) in.readObject(); in.close(); fileIn.close(); } } //catch(IOException i) //{ // i.printStackTrace(); // return; //}catch(ClassNotFoundException c) //{ // c.printStackTrace(); // return; //} catch(Exception e) { e.printStackTrace(); listRxStr = Collections.synchronizedList(new ArrayList<String>()); mapPayloads = new ConcurrentHashMap<String, Payload>(); return; } } }