package de.tu.darmstadt.seemoo.ansian.model.demodulation.morse;
import java.util.Arrays;
import android.util.Log;
import de.greenrobot.event.EventBus;
import de.greenrobot.event.Subscribe;
import de.tu.darmstadt.seemoo.ansian.control.events.morse.MorseCodeEvent;
import de.tu.darmstadt.seemoo.ansian.control.events.morse.MorseDitEvent;
import de.tu.darmstadt.seemoo.ansian.control.events.morse.MorseSymbolEvent;
import de.tu.darmstadt.seemoo.ansian.model.ErrorBitSet;
import de.tu.darmstadt.seemoo.ansian.model.FFTSample;
import de.tu.darmstadt.seemoo.ansian.model.demodulation.morse.Morse.Mode;
import de.tu.darmstadt.seemoo.ansian.model.preferences.DemodFrequencyEvent;
import de.tu.darmstadt.seemoo.ansian.model.preferences.Preferences;
import de.tu.darmstadt.seemoo.ansian.tools.morse.Decoder;
import de.tu.darmstadt.seemoo.ansian.tools.morse.MorseCodeCharacterGetter;
public class MorseStats {
private static final String LOGTAG = "MorseStats";
private int dit;
private int dah;
private int word;
private ErrorBitSet codeSuccess = new ErrorBitSet(100);
private ErrorBitSet symbolSuccess = new ErrorBitSet(30);
private int offset = 0;
private float threshold;
private int[] stats;
private MorseBitSet bits;
private int demodBandwidth;
private int sampleDuration;
private StringBuilder currentSymbolCode = new StringBuilder();
private Decoder decoder = new Decoder();
private long demodFrequency;
private float low;
private float high;
public MorseStats(FFTSample[] fftSamples, int timeMs) {
this();
sampleDuration = timeMs / fftSamples.length;
calcThreshold(fftSamples);
bits = new MorseBitSet(fftSamples, this);
calcTimings();
}
private MorseStats() {
EventBus.getDefault().register(this);
high = -Float.MAX_VALUE;
low = Float.MAX_VALUE;
demodBandwidth = Preferences.GUI_PREFERENCE.getDemodBandwidth();
demodFrequency = Preferences.GUI_PREFERENCE.getDemodFrequency();
}
public MorseStats(int ditDuration) {
this();
setTimings(ditDuration);
threshold = Preferences.GUI_PREFERENCE.getSquelch();
}
private void setTimings(int dit) {
this.dit = dit;
dah = 3 * dit;
word = 7 * dit;
offset = (int) Math.round(dit * 0.5);
}
public boolean decode(boolean high, long l) {
if (l < dit - offset)
return false;
String code = null;
// high
if (high) {
// dit
if (dit - offset < l && l < dit + offset) {
code = ".";
}
// dah
if (dah - dit < l && l < dah + dit) {
code = "-";
}
}
// low
else {
// dit
if (dit - offset < l && l < dit + offset) {
code = "";
}
// dah
if (dah - dit < l && l < dah + dit) {
code = " ";
}
// word
if (word - 2 * dit < l) {
code = "/";
}
// pause/no signal
if (word + offset < l) {
code = "";
}
}
// check if code recognized
if (code == null) {
// not in range
codeSuccess.setBit(false);
code = "[not in range: " + l + "]";
} else {
codeSuccess.setBit(true);
currentSymbolCode.append(code);
}
Log.d(LOGTAG, high + " time in ms: " + l + " code: " + code);
EventBus.getDefault()
.postSticky(new MorseCodeEvent(code, high, l, codeSuccess.getSuccessRate(), getThreshold()));
// check if new symbol
if (code == " ") {
createSymbol(code);
} else
currentSymbolCode.append(code);
return true;
}
private void createSymbol(String code) {
String currentSymbolCodeString = currentSymbolCode.toString();
String symbol = decoder.decode(currentSymbolCodeString);
boolean recognized = !(symbol.contains(MorseCodeCharacterGetter.ESCAPE_START)
|| symbol.contains(MorseCodeCharacterGetter.ESCAPE_END));
symbolSuccess.setBit(recognized);
float successRate = getSymbolSuccessRate();
EventBus.getDefault()
.postSticky(new MorseSymbolEvent(currentSymbolCodeString, symbol, recognized, successRate));
currentSymbolCode = new StringBuilder();
}
private void calcTimings() {
stats = new int[bits.getLength()];
int num = 0;
for (int i = 0; i < bits.getLength();) {
num = bits.getNextBits(i);
stats[Math.abs(num)]++;
i += Math.abs(num);
}
Log.d(LOGTAG, " Stats: " + Arrays.toString(stats));
int samples = findPosOfMaxOccurance(sumNeighbours(stats, 1)) + 1;
setTimings(samples * sampleDuration);
EventBus.getDefault().postSticky(new MorseDitEvent(dit));
}
private int findPosOfMaxOccurance(int[] stats) {
int pos = 0;
int value = 0;
for (int i = 0; i < stats.length; i++) {
if (stats[i] >= value) {
pos = i;
value = stats[i];
}
}
return pos;
}
private int[] sumNeighbours(int[] stats, int x) {
int[] result = Arrays.copyOf(stats, stats.length);
for (int i = 0; i < result.length; i++) {
for (int j = 1; j <= x; j++) {
if (i - j > 0)
result[i] += stats[i - j];
if (i + j < stats.length)
result[i] += stats[i + j];
}
}
Log.d(LOGTAG, " Neighborstats: " + Arrays.toString(result));
return result;
}
private void calcThreshold(FFTSample[] fftSamples) {
for (int i = 0; i < fftSamples.length; i++) {
calcThreshold(fftSamples[i]);
}
}
private float getThreshold() {
if (Morse.getMode() == Mode.MANUAL)
return Preferences.GUI_PREFERENCE.getSquelch();
else
return threshold;
}
public boolean estimateValue(FFTSample sample) {
return sample.getAverage(demodFrequency, demodBandwidth) > getThreshold();
}
public float getSymbolSuccessRate() {
return codeSuccess.getSuccessRate();
}
@Subscribe
public void onEvent(DemodFrequencyEvent event) {
demodFrequency = event.getDemodFrequency();
if (Morse.getMode() != Mode.MANUAL) {
high = -Float.MAX_VALUE;
low = Float.MAX_VALUE;
}
}
public void calcThreshold(FFTSample sample) {
float average = sample.getAverage(demodFrequency, demodBandwidth);
high = Math.max(high, average);
low = Math.min(low, average);
threshold = low + (high - low) / 2;
}
public boolean checkStats() {
return (symbolSuccess.checkStats() && symbolSuccess.checkStats());
}
}