package org.geogebra.desktop.plugin;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.move.ggtapi.models.json.JSONArray;
import org.geogebra.common.move.ggtapi.models.json.JSONObject;
import org.geogebra.common.plugin.SensorLogger;
import org.geogebra.common.util.Charsets;
import org.geogebra.common.util.debug.Log;
/**
* @author michael
*
* class to listen to UDP packets and then eg update slider with the
* values
*
*/
public class UDPLoggerD extends SensorLogger {
@SuppressWarnings("javadoc")
Thread thread;
@SuppressWarnings("javadoc")
DatagramSocket dsocket;
private long now;
/**
* @param kernel
* kernel
*/
public UDPLoggerD(Kernel kernel) {
super(kernel);
}
@Override
public void closeSocket() {
if (dsocket != null) {
dsocket.close();
// stops thread
dsocket = null;
}
}
private static float getFloat(byte[] buffer1, int i) {
byte[] bytes = { buffer1[i], buffer1[i + 1], buffer1[i + 2],
buffer1[i + 3] };
return ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getFloat();
}
public void handleJSON(byte[] buffer, int length, String address,
boolean quicker) {
// TODO: convert to-from string by a specific encoding, e.g. UTF-8
try {
JSONArray ja = new JSONArray(
new String(buffer, 0, length, Charsets.UTF_8));
JSONObject jo;
String key;
if ("EDAQ".equals(ja.getString(0))) {
// EDAQ 530
// "EDAQ;{sensor1},{doublebits8};{sensor},{doublebits};{sensor},{doublebits}"...
// we could even spare the ; and , but still left
boolean atleast = true;
for (int bp = 1; bp < ja
.length(); bp++, atleast = (bp + 1 < ja.length())) {
jo = ja.getJSONObject(bp);
key = jo.keys().next();
double timestamp = 0;
switch (Integer.parseInt(key)) {
case 0:
log(Types.EDAQ0, timestamp, jo.getDouble(key), false,
!quicker, atleast);
break;
case 1:
log(Types.EDAQ1, timestamp, jo.getDouble(key), false,
!quicker, atleast);
break;
case 2:
log(Types.EDAQ2, timestamp, jo.getDouble(key), false,
!quicker, atleast);
break;
default:
Log.error("unknown EDAQ port!");
}
}
// flush repainting of logs!
kernel.notifyRepaint();
}
} catch (Exception e) {
Log.error("problem with EDAQ port: " + e.getMessage());
}
}
public void handle(byte[] buffer, int length, String address,
boolean quicker) {
// Log.debug("undoActive is: " + kernel.isUndoActive());
byte c0 = buffer[0];
byte c1 = buffer[1];
byte c2 = buffer[2];
byte c3 = buffer[3];
if (c0 == 'F' && c1 == 'S' && c2 == 0x01) {
double timestamp = 0;
// https://itunes.apple.com/gb/app/sensor-data-streamer/id608278214?mt=8
Log.debug("data is from 'Sensor Streamer' (c) 2013 FNI Co LTD");
log(Types.ACCELEROMETER_X, timestamp, getFloat(buffer, 4));
log(Types.ACCELEROMETER_Y, timestamp, getFloat(buffer, 8));
log(Types.ACCELEROMETER_Z, timestamp, getFloat(buffer, 12));
log(Types.ORIENTATION_X, timestamp, getFloat(buffer, 16));
log(Types.ORIENTATION_Y, timestamp, getFloat(buffer, 20));
log(Types.ORIENTATION_Z, timestamp, getFloat(buffer, 24));
log(Types.MAGNETIC_FIELD_X, timestamp, getFloat(buffer, 28));
log(Types.MAGNETIC_FIELD_Y, timestamp, getFloat(buffer, 36));
log(Types.MAGNETIC_FIELD_Z, timestamp, getFloat(buffer, 44));
} else if (c0 == 'E' && c1 == 'D' && c2 == 'A' && c3 == 'Q') {
// EDAQ 530
// "EDAQ;{sensor1},{doublebits8};{sensor},{doublebits};{sensor},{doublebits}"...
// we could even spare the ; and , but still left
boolean atleast = true;
for (int bp = 5; bp < length; bp += 11, atleast = (bp
+ 11 < length)) {
// "{sensor1},{doublebits8};"
// ,,23456789
if (buffer[bp - 1] != ';') {
Log.error("error in UDP transmission");
}
long gotit = 0;
for (int place = bp + 2, shift = 56; place < bp
+ 10; place++, shift -= 8) {
gotit |= ((buffer[place] & 0xFFL) << shift);
}
if (buffer[bp + 1] != ',') {
Log.error("error in UDP transmission");
}
double timestamp = 0;
switch (buffer[bp]) {
case 0:
log(Types.EDAQ0, timestamp, Double.longBitsToDouble(gotit),
false, !quicker, atleast);
break;
case 1:
log(Types.EDAQ1, timestamp, Double.longBitsToDouble(gotit),
false, !quicker, atleast);
break;
case 2:
log(Types.EDAQ2, timestamp, Double.longBitsToDouble(gotit),
false, !quicker, atleast);
break;
default:
Log.error("unknown EDAQ port!");
}
}
// flush repainting of logs!
kernel.notifyRepaint();
} else {
// https://play.google.com/store/apps/details?id=jp.ac.ehime_u.cite.sasaki.SensorUdp
Log.debug("Assume data is from Android/SensorUDP");
String msg;
try {
msg = new String(buffer, 0, length, Charsets.UTF_8);
} catch (UnsupportedEncodingException e) {
return;
}
Log.debug(msg);
String[] split = msg.split(", ");
double timestamp = Math.abs(now - Double.parseDouble(split[2]));
switch (buffer[0]) {
case 'A':
// Log.debug("received" + msg);
log(Types.ACCELEROMETER_X, timestamp,
Double.parseDouble(split[3].replace(",", ".")));
log(Types.ACCELEROMETER_Y, timestamp,
Double.parseDouble(split[4].replace(",", ".")));
log(Types.ACCELEROMETER_Z, timestamp,
Double.parseDouble(split[5].replace(",", ".")));
break;
case 'M':
log(Types.MAGNETIC_FIELD_X, timestamp,
Double.parseDouble(split[3].replace(",", ".")));
log(Types.MAGNETIC_FIELD_Y, timestamp,
Double.parseDouble(split[4].replace(",", ".")));
log(Types.MAGNETIC_FIELD_Z, timestamp,
Double.parseDouble(split[5].replace(",", ".")));
break;
case 'O':
log(Types.ORIENTATION_X, timestamp,
Double.parseDouble(split[3].replace(",", ".")));
log(Types.ORIENTATION_Y, timestamp,
Double.parseDouble(split[4].replace(",", ".")));
log(Types.ORIENTATION_Z, timestamp,
Double.parseDouble(split[5].replace(",", ".")));
break;
default:
// Log.debug("unknown data type: " + buffer[0]);
// Log.debug(msg);
break;
}
// timestamp and data-count logged always
log(Types.TIMESTAMP, timestamp, timestamp);
log(Types.DATA_COUNT, timestamp, Double.parseDouble(split[1]));
// for (int i = 1; i < split.length; i++) {
// Log.debug(split[i]);
// }
// Convert the contents to a string, and display
// them
Log.debug(address + ": " + msg);
}
}
@Override
public boolean startLogging() {
now = System.currentTimeMillis();
initStartLogging();
// Create a socket to listen on the port.
try {
if (dsocket != null) {
dsocket.close();
}
dsocket = new DatagramSocket(getUDPPort());
// Don't block more than 3 sec...
// useful in case the datagram flow stops,
// or is not yet started at all
dsocket.setSoTimeout(3000);
} catch (SocketException e) {
e.printStackTrace();
return false;
}
// Create a buffer to read datagrams into. If a
// packet is larger than this buffer, the
// excess will simply be discarded!
// final byte[] buffer = new byte[2048];
// EDAQx's internal frequency can be 1000 Hz, and this can send
// 1000 * 3 * 11 bytes in one second
// however, it's actually sending the data with pauses of 50ms or more,
// with not more than 200 * 11 bytes at once, and this gives:
// a maximum of (1000/50) * (200 * 11) bytes in one second
// that is, 20 * 200 * 11, or 1000 * 4 * 11... however, the 50ms
// is more than that because of computation times, so the
// 1000 * 4 * 11 can be less, and in this case, maybe the software
// is not able to transmit the 1000 * 3 * 11 in this rate...
// Possible solution: increase the bytes it can send at once,
// to its double! Now we can do it, a piece of data needs only
// 11 bytes, not 33 bytes... however, we should not change the 50ms...
// so trying 1000 bytes of original buffer instead of 400 in
// SerialReader.java,
// and this gives 500 * 11 bytes at once! Plus the EDAQ string.
// this happens e.g. if the 50ms of waiting time is interrupted
// by many ms of computer processing time.
// ??
// buffer size is computed in SerialReader.java in EDAQx branch
// this is the maximal possible value (maybe more), but it can be much
// smaller if the sample rate is less than 1000Hz and there are less
// sensors
// used than 3... and it can be also larger if time data will be sent...
// so in the future, it would be good to get the sample rate frequency
// data and compute the buffer size dynamically here (especially for
// old machines)
final byte[] buffer = new byte[15551];
// of course, this will only be used when needed, and anyway, 6 KB
// is not much in the age of kilobytes and megabytes...
// Create a packet to receive data into the buffer
final DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
// Now loop forever, waiting to receive packets and printing them.
// if this changes, another thread has started -> terminate this one
final DatagramSocket socketCopy = dsocket;
// Maybe important!!! We should neglect all information from the sensors
// coming from the past! Because it may happen that the sensors were
// switched on much earlier than we switched on receiving their data,
// and in this case, so much lag can be introduced which cannot be
// conquered in a reasonable time. So, we should storno the socket
// somehow, discard all datagram packets already sent... but how?
// This doesn't work:
/*
* try { dsocket.receive(packet); while (packet.getLength() > 0) { if
* (socketCopy != dsocket) { dsocket.close(); break; } try {
* dsocket.receive(packet); } catch (Exception ex) { dsocket.close();
* break; } } } catch (Exception ex) { dsocket.close(); }
*
* if (dsocket.isClosed()) { // do not even start the Thread return
* false; }
*/
thread = new Thread() {
@Override
public void run() {
Log.debug("thread starting");
while (socketCopy == dsocket) {
// Wait to receive a datagram
try {
Log.debug("waiting");
dsocket.receive(packet);
} catch (SocketTimeoutException e) {
closeSocket(e);
// stoplogging also drops exception here, so no need
// error message if
// stoplogging called
kernel.getApplication()
.showError(kernel.getApplication()
.getLocalization()
.getMenu("LoggingError"));
} catch (IOException e) {
closeSocket(e);
}
if (socketCopy == dsocket) {
synchronized (kernel.getConcurrentModificationLock()) {
// final byte[] bufferCopy = buffer.clone();
final int length = packet.getLength();
// SwingUtilities.invokeLater(new Runnable() {
// public void run() {
try {
if ("[".getBytes(
Charsets.UTF_8)[0] == buffer[0]) {
handleJSON(buffer, length,
packet.getAddress().getHostAddress()
+ " "
+ packet.getAddress()
.getHostName(),
false);
} else {
handle(buffer, length,
packet.getAddress().getHostAddress()
+ " "
+ packet.getAddress()
.getHostName(),
false);
}
} catch (UnsupportedEncodingException e) {
// do nothing
}
// }
// });
// Reset the length of the packet before reusing
// it.
packet.setLength(buffer.length);
}
}
}
if (dsocket != null) {
dsocket.close();
}
Log.debug("thread ending");
}
};
thread.start();
return true;
}
protected void closeSocket(IOException e) {
if (dsocket != null) {
dsocket.close();
}
dsocket = null;
Log.debug("logging failed");
e.printStackTrace();
}
}