package dk.silverbullet.telemed.questionnaire.node;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.*;
import com.google.gson.annotations.Expose;
import dk.silverbullet.telemed.OpenTeleApplication;
import dk.silverbullet.telemed.device.AmbiguousDeviceException;
import dk.silverbullet.telemed.device.BluetoothDisabledException;
import dk.silverbullet.telemed.device.BluetoothNotAvailableException;
import dk.silverbullet.telemed.device.DeviceInitialisationException;
import dk.silverbullet.telemed.device.monica.MonicaDevice;
import dk.silverbullet.telemed.device.monica.MonicaDeviceController;
import dk.silverbullet.telemed.questionnaire.Questionnaire;
import dk.silverbullet.telemed.questionnaire.R;
import dk.silverbullet.telemed.questionnaire.expression.Expression;
import dk.silverbullet.telemed.questionnaire.expression.UnknownVariableException;
import dk.silverbullet.telemed.questionnaire.expression.Variable;
import dk.silverbullet.telemed.questionnaire.expression.VariableLinkFailedException;
import dk.silverbullet.telemed.questionnaire.node.monica.DeviceState;
import dk.silverbullet.telemed.questionnaire.node.monica.MonicaDeviceCallback;
import dk.silverbullet.telemed.questionnaire.node.monica.SimulatedMonicaDevice;
import dk.silverbullet.telemed.utils.ProgressiveProgress;
import dk.silverbullet.telemed.utils.Util;
import java.util.*;
import static dk.silverbullet.telemed.utils.Json.ISO8601_DATE_TIME_FORMAT;
import static dk.silverbullet.telemed.utils.Util.linkVariable;
public class MonicaDeviceNode extends DeviceNode implements MonicaDeviceCallback, SeekBar.OnSeekBarChangeListener {
private static final String TAG = Util.getTag(MonicaDeviceNode.class);
ProgressiveProgress progressiveProgress;
@Expose
private Expression<Boolean> runAsSimulator; // Set to true to run as simulation only
@Expose
private Expression<Integer> measuringTime; // Number of minutes to get samples from the device
@Expose
private Variable<Float[]> fhr; // (Fetal Heart Rate, fostrets hjerterytme)
@Expose
private Variable<Float[]> mhr; // (Maternal Heart Rate - moderens hjerterytme)
@Expose
private Variable<Integer[]> qfhr; // (Quality measurements for FHR - noget med hvor godt signalet er 0..3)
@Expose
private Variable<Integer[]> fetalHeight;
@Expose
private Variable<Integer[]> signalToNoise;
@Expose
private Variable<Float[]> toco; // Livmoderbevægelser
@Expose
private Variable<String[]> signal; // tidspunkter for tryk på den lyserøde knap, ISO-8601 -format
@Expose
private Variable<Float> voltageStart; // batterispænding ved start
@Expose
private Variable<Float> voltageEnd; // batterispænding ved afslutning
private transient List<float[]> mhrBuf;
private transient List<float[]> fhrBuf;
private transient List<float[]> tocoBuf;
private transient List<int[]> qfhrBuf;
private transient List<Integer> fetalHeightBuf;
private transient List<Integer> signalToNoiseBuf;
private transient List<String> signalBuf;
private transient TextView progressStatusText;
private transient ProgressBar mainProgress;
private transient SeekBar measureTime;
private transient TextView measureTimeText;
private transient LinearLayout userTimeSet;
private transient CheckBox buttonOrange;
private transient CheckBox buttonWhite;
private transient CheckBox buttonGreen;
private transient CheckBox buttonBlack;
private transient CheckBox buttonYellow;
private int sampleTimeInMinutes = 30;
private long start;
private DeviceState currentState;
private MonicaDevice device;
public MonicaDeviceNode(Questionnaire questionnaire, String nodeName) {
super(questionnaire, nodeName);
}
@Override
public void enter() {
Log.d(TAG, "Enter...");
super.enter();
Activity activity = questionnaire.getActivity();
inflateView(activity);
initialize(activity);
try {
if (runAsSimulator != null && runAsSimulator.evaluate() != null && runAsSimulator.evaluate())
device = new SimulatedMonicaDevice(this);
else
device = new MonicaDeviceController(this, activity);
} catch (BluetoothDisabledException e) {
OpenTeleApplication.instance().logException(e);
Log.d(TAG, "Exception: " + e);
Toast.makeText(questionnaire.getContext().getApplicationContext(), Util.getString(R.string.monica_bluetooth_off, questionnaire),
Toast.LENGTH_LONG).show();
questionnaire.setCurrentNode(getNextFailNode());
} catch (BluetoothNotAvailableException e) {
OpenTeleApplication.instance().logException(e);
Log.d(TAG, "Exception: " + e);
Toast.makeText(questionnaire.getContext().getApplicationContext(),
Util.getString(R.string.monica_bluetooth_unavaliable, questionnaire), Toast.LENGTH_LONG).show();
questionnaire.setCurrentNode(getNextFailNode());
} catch (AmbiguousDeviceException e) {
OpenTeleApplication.instance().logException(e);
Log.d(TAG, "Exception: " + e);
Toast.makeText(questionnaire.getContext().getApplicationContext(),
Util.getString(R.string.monica_multiple_devices, questionnaire), Toast.LENGTH_LONG).show();
questionnaire.setCurrentNode(getNextFailNode());
} catch (DeviceInitialisationException e) {
OpenTeleApplication.instance().logException(e);
Log.d(TAG, "Exception: " + e);
Toast.makeText(questionnaire.getContext().getApplicationContext(), Util.getString(R.string.monica_failed_to_start, questionnaire),
Toast.LENGTH_LONG).show();
questionnaire.setCurrentNode(getNextFailNode());
}
}
private void inflateView(Context context) {
ViewGroup rootLayout = questionnaire.getRootLayout();
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View monicaView = inflater.inflate(R.layout.monica, null);
rootLayout.removeAllViews();
rootLayout.addView(monicaView);
}
private void initialize(Activity activity) {
resolveViews(activity);
progressiveProgress = new ProgressiveProgress(15, 10, 5, 15, 30);
Log.d(TAG, "measuringTime: " + measuringTime);
if (measuringTime == null || measuringTime.evaluate() == null || measuringTime.evaluate() == 0) {
measureTime.setOnSeekBarChangeListener(this);
measureTime.setMax(progressiveProgress.getStepCount() - 1);
sampleTimeInMinutes = 30;
measureTime.setProgress(progressiveProgress.value2step(sampleTimeInMinutes));
} else {
sampleTimeInMinutes = measuringTime.evaluate();
userTimeSet.setVisibility(View.GONE);
}
buttonOrange.setChecked(false);
buttonWhite.setChecked(false);
buttonGreen.setChecked(false);
buttonBlack.setChecked(false);
buttonYellow.setChecked(false);
final int SAMPLE_SECONDS = 3600;
mhrBuf = new ArrayList<float[]>(SAMPLE_SECONDS);
fhrBuf = new ArrayList<float[]>(SAMPLE_SECONDS);
tocoBuf = new ArrayList<float[]>(SAMPLE_SECONDS);
qfhrBuf = new ArrayList<int[]>(SAMPLE_SECONDS);
fetalHeightBuf = new ArrayList<Integer>(SAMPLE_SECONDS / 20);
signalToNoiseBuf = new ArrayList<Integer>(SAMPLE_SECONDS / 20);
signalBuf = new ArrayList<String>();
}
private void resolveViews(Activity activity) {
mainProgress = (ProgressBar) activity.findViewById(R.id.mainProgress);
userTimeSet = (LinearLayout) activity.findViewById(R.id.userTimeSet);
progressStatusText = (TextView) activity.findViewById(R.id.progressStatusText);
measureTime = (SeekBar) activity.findViewById(R.id.measureTime);
measureTimeText = (TextView) activity.findViewById(R.id.measureTimeText);
buttonOrange = (CheckBox) activity.findViewById(R.id.buttonOrange);
buttonWhite = (CheckBox) activity.findViewById(R.id.buttonWhite);
buttonGreen = (CheckBox) activity.findViewById(R.id.buttonGreen);
buttonBlack = (CheckBox) activity.findViewById(R.id.buttonBlack);
buttonYellow = (CheckBox) activity.findViewById(R.id.buttonYellow);
}
private String[] asStringArray(List<String> stringList) {
String[] stringArray = new String[stringList.size()];
int i = 0;
for (String string : stringList) {
stringArray[i++] = string;
}
return stringArray;
}
private Float[] asFloatArray(List<float[]> mhr2) {
int size = 0;
for (float[] fa : mhr2) {
size += fa.length;
}
Float[] result = new Float[size];
int i = 0;
for (float[] fa : mhr2) {
for (float f : fa) {
result[i++] = f;
}
}
return result;
}
private Integer[] asIntegerArray(List<int[]> mhr2) {
int size = 0;
for (int[] fa : mhr2) {
size += fa.length;
}
Integer[] result = new Integer[size];
int i = 0;
for (int[] ia : mhr2) {
for (int ii : ia) {
result[i++] = ii;
}
}
return result;
}
@Override
public void addSamples(float[] mhr, float[] fhr, int[] qfhr, float[] toco, Date readTime) {
this.mhrBuf.add(mhr);
this.fhrBuf.add(fhr);
this.qfhrBuf.add(qfhr);
this.tocoBuf.add(toco);
}
@Override
public void updateProgress(final int current, final int total) {
questionnaire.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
mainProgress.setMax(total);
mainProgress.setProgress(current);
setState(DeviceState.RECEIVING_DATA);
// int pct = (current * 100) / total;
// progressText.setText(pct + "%");
}
});
}
@Override
public void setProbeState(final boolean orange, final boolean white, final boolean green, final boolean black,
final boolean yellow) {
questionnaire.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
buttonOrange.setChecked(orange);
buttonWhite.setChecked(white);
buttonGreen.setChecked(green);
buttonBlack.setChecked(black);
buttonYellow.setChecked(yellow);
}
});
}
@Override
public void setState(DeviceState state) {
if (currentState == state)
return;
currentState = state;
final String text;
switch (currentState) {
case WAITING_FOR_CONNECTION:
text = Util.getString(R.string.monica_waiting_for_connection, questionnaire);
break;
case CHECKING_STARTING_CONDITION:
text = Util.getString(R.string.monica_checking_connection, questionnaire);
break;
case WAITING_FOR_DATA:
text = Util.getString(R.string.monica_waiting_for_data, questionnaire);
break;
case RECEIVING_DATA:
text = Util.getString(R.string.monica_receiving_data, questionnaire);
break;
case PROCESSING_DATA:
text = Util.getString(R.string.monica_processing_data, questionnaire);
break;
case CLOSING:
text = Util.getString(R.string.monica_closing, questionnaire);
break;
default:
text = Util.getString(R.string.monica_question_marks, questionnaire);
break;
}
questionnaire.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
progressStatusText.setText(text);
}
});
}
@SuppressWarnings("unchecked")
@Override
public void linkVariables(Map<String, Variable<?>> variablePool) throws VariableLinkFailedException {
super.linkVariables(variablePool);
fhr = linkVariable(variablePool, fhr);
mhr = linkVariable(variablePool, mhr);
qfhr = linkVariable(variablePool, qfhr);
fetalHeight = linkVariable(variablePool, fetalHeight);
signalToNoise = linkVariable(variablePool, signalToNoise);
toco = linkVariable(variablePool, toco);
signal = linkVariable(variablePool, signal);
voltageStart = linkVariable(variablePool, voltageStart);
voltageEnd = linkVariable(variablePool, voltageEnd);
if (runAsSimulator != null) {
if (runAsSimulator instanceof Variable) {
String name = ((Variable<Boolean>) runAsSimulator).getName();
if (variablePool.containsKey(name))
runAsSimulator = (Expression<Boolean>) variablePool.get(name);
else
throw new UnknownVariableException(name);
} else
runAsSimulator.link(variablePool);
}
if (measuringTime != null) {
if (measuringTime instanceof Variable) {
String name = ((Variable<Integer>) measuringTime).getName();
if (variablePool.containsKey(name)) {
measuringTime = (Expression<Integer>) variablePool.get(name);
} else
throw new UnknownVariableException(name);
} else
measuringTime.link(variablePool);
}
}
@Override
public void abort(final String message) {
questionnaire.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
progressStatusText.setText(message);
Toast.makeText(questionnaire.getContext().getApplicationContext(), message, Toast.LENGTH_LONG).show();
questionnaire.setCurrentNode(getNextFailNode());
}
});
}
@Override
public void done(final String message) {
questionnaire.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
progressStatusText.setText(message);
Toast.makeText(questionnaire.getContext().getApplicationContext(), message, Toast.LENGTH_LONG).show();
questionnaire.setCurrentNode(getNextNode());
}
});
}
@Override
public void done() {
Log.d(TAG, "Done!");
questionnaire.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
questionnaire.setCurrentNode(getNextNode());
}
});
}
@Override
public void setStartTimeValue(Date startTime) {
super.setStartTimeValue(startTime);
start = startTime.getTime();
};
@Override
public Date getStartTimeValue() {
if (start == 0)
return null;
return new Date(start);
}
@Override
public void setStartVoltage(float voltage) {
Log.d(TAG, "start voltage: " + voltage);
if (voltageStart != null)
voltageStart.setValue(voltage);
}
@Override
public void setEndVoltage(float voltage) {
Log.d(TAG, "end voltage: " + voltage);
if (voltageEnd != null)
voltageEnd.setValue(voltage);
}
public Expression<Boolean> getRunAsSimulator() {
return runAsSimulator;
}
public void setRunAsSimulator(Expression<Boolean> runAsSimulator) {
this.runAsSimulator = runAsSimulator;
}
public Variable<Float[]> getFhr() {
return fhr;
}
public void setFhr(Variable<Float[]> fhr) {
this.fhr = fhr;
}
public Variable<Float[]> getMhr() {
return mhr;
}
public void setMhr(Variable<Float[]> mhr) {
this.mhr = mhr;
}
public Variable<Integer[]> getQfhr() {
return qfhr;
}
public void setFetalHeight(Variable<Integer[]> fetalHeight) {
this.fetalHeight = fetalHeight;
}
public void setSignalToNoise(Variable<Integer[]> signalToNoise) {
this.signalToNoise = signalToNoise;
}
public void setQfhr(Variable<Integer[]> qfhr) {
this.qfhr = qfhr;
}
public Variable<Float[]> getToco() {
return toco;
}
public void setToco(Variable<Float[]> toco) {
this.toco = toco;
}
public Variable<String[]> getSignal() {
return signal;
}
public void setSignal(Variable<String[]> signal) {
this.signal = signal;
}
public Variable<Float> getVoltageStart() {
return voltageStart;
}
public void setVoltageStart(Variable<Float> voltageStart) {
this.voltageStart = voltageStart;
}
public Variable<Float> getVoltageEnd() {
return voltageEnd;
}
public void setVoltageEnd(Variable<Float> voltageEnd) {
this.voltageEnd = voltageEnd;
}
@Override
public void addSignal(Date dateTime) {
Log.d(TAG, "dt=" + dateTime);
Log.d(TAG, "dtF=" + ISO8601_DATE_TIME_FORMAT.format(dateTime));
Log.d(TAG, "sb=" + signalBuf);
Log.d(TAG, "sbZ=" + signalBuf.size());
signalBuf.add(ISO8601_DATE_TIME_FORMAT.format(dateTime));
}
@Override
public void deviceLeave() {
if (device != null)
device.close();
mhr.setValue(asFloatArray(mhrBuf));
mhrBuf.clear();
fhr.setValue(asFloatArray(fhrBuf));
fhrBuf.clear();
qfhr.setValue(asIntegerArray(qfhrBuf));
qfhrBuf.clear();
fetalHeight.setValue(fetalHeightBuf.toArray(new Integer[fetalHeightBuf.size()]));
fetalHeightBuf.clear();
signalToNoise.setValue(signalToNoiseBuf.toArray(new Integer[signalToNoiseBuf.size()]));
signalToNoiseBuf.clear();
// fmp.setExpressionValue(asIntegerArray(fmpBuf)); // ??
toco.setValue(asFloatArray(tocoBuf));
tocoBuf.clear();
signal.setValue(asStringArray(signalBuf));
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (!fromUser)
return; // Ignore setting the progress time internally!
int progressTime = updateMeasuringTime(progress);
measureTimeText.setText(Util.getString(R.string.monica_set_time, questionnaire) + getSampleTimeText(progressTime));
}
private int updateMeasuringTime(int progress) {
int min = calculateMinTime();
int progressTime = progressiveProgress.step2value(progress);
if (progressTime < min) {
progressTime = min;
measureTime.setProgress(progressiveProgress.value2step(progressTime));
} else if (progressTime > progressiveProgress.getHighestValue()) {
progressTime = progressiveProgress.getHighestValue();
measureTime.setProgress(progressiveProgress.getStepCount());
}
return progressTime;
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
int progressTime = updateMeasuringTime(seekBar.getProgress());
measureTimeText.setText(Util.getString(R.string.monica_set_time, questionnaire) + getSampleTimeText(progressTime));
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
sampleTimeInMinutes = updateMeasuringTime(seekBar.getProgress());
measureTimeText.setText(Util.getString(R.string.monica_time_set_to, questionnaire) + getSampleTimeText(sampleTimeInMinutes));
}
private String getSampleTimeText(int minutes) {
String sampleTimeText = minutes / 60 + ":";
if (minutes % 60 < 10)
sampleTimeText += "0";
sampleTimeText += minutes % 60;
return sampleTimeText;
}
private int calculateMinTime() {
if (start == 0)
return 15;
int duration = (int) ((System.currentTimeMillis() - start + 60000) / 60000);
return Math.max(15, duration);
}
@Override
public int getSampleTimeMinutes() {
return sampleTimeInMinutes;
}
public void setMeasuringTime(Expression<Integer> measuringTime) {
this.measuringTime = measuringTime;
}
public Expression<Integer> getMeasuringTime() {
return measuringTime;
}
@Override
public void addFetalHeight(int fetalHeight) {
fetalHeightBuf.add(fetalHeight);
}
@Override
public void addSignalToNoise(int signalToNoise) {
signalToNoiseBuf.add(signalToNoise);
}
}