package org.geogebra.common.plugin; import java.util.HashMap; import java.util.Iterator; import java.util.Map.Entry; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.arithmetic.ExpressionValue; import org.geogebra.common.kernel.arithmetic.MyDouble; import org.geogebra.common.kernel.arithmetic.MyList; import org.geogebra.common.kernel.arithmetic.MyNumberPair; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoFunction; import org.geogebra.common.kernel.geos.GeoList; import org.geogebra.common.kernel.geos.GeoNumeric; import org.geogebra.common.kernel.geos.GeoText; import org.geogebra.common.util.debug.Log; public abstract class SensorLogger { public static final int DEFAULT_LIMIT = 20000; /** kernel */ protected final Kernel kernel; /** * port to receive UDP logging on */ private int port = 7166; public String appID = "ABCD"; public boolean oldUndoActive = false; public static enum Types { // DON'T CHANGE STRINGS - USED IN XML FOR DATA COLLECTION VIEW TIMESTAMP("time"), ACCELEROMETER_X("Ax", true), ACCELEROMETER_Y("Ay", true), ACCELEROMETER_Z("Az", true), ORIENTATION_X("Ox", true), ORIENTATION_Y("Oy", true), ORIENTATION_Z("Oz", true), MAGNETIC_FIELD_X("Mx", true), MAGNETIC_FIELD_Y("My", true), MAGNETIC_FIELD_Z("Mz", true), PROXIMITY("proximity", true), LIGHT("light", true), LOUDNESS("loudness", true), DATA_COUNT("datacount"), EDAQ0("EDAQ0", true), EDAQ1("EDAQ1", true), EDAQ2("EDAQ2", true), PORT("port", true), APP_ID("appID"), MOBILE_FOUND("mobile_found"), FREQUENCY("frequency"); private String string; private boolean storeInXML = false; Types(String s) { this.string = s; } Types(String s, boolean storeInXML) { this.string = s; this.storeInXML = storeInXML; } @Override public String toString() { return string; } public String toXMLString() { return string; } public static Types lookup(String s) { for (Types type : Types.values()) { if (type.string.equals(s)) { return type; } } return null; } public boolean storeInXML() { return storeInXML; } } protected HashMap<Types, GeoNumeric> listeners = new HashMap<Types, GeoNumeric>(); protected HashMap<Types, GeoList> listenersL = new HashMap<Types, GeoList>(); protected HashMap<Types, GeoFunction> listenersF = new HashMap<Types, GeoFunction>(); protected HashMap<Types, Integer> listLimits = new HashMap<Types, Integer>(); protected HashMap<Types, Integer> listenersAges = new HashMap<Types, Integer>(); private int stepsToGo = DEFAULT_LIMIT; protected SensorLogger(Kernel kernel) { this.kernel = kernel; } protected int getUDPPort() { return port; } public abstract boolean startLogging(); protected abstract void closeSocket(); public void registerGeo(String s, GeoElement geo) { Types type = Types.lookup(s); if (type != null) { if (type == Types.PORT) { port = (int) ((GeoNumeric) geo).getValue(); } else if (type == Types.APP_ID) { appID = ((GeoText) geo).getTextString(); Log.debug(appID); } else { prepareRegister(type, geo, 0); listeners.put(type, (GeoNumeric) geo); } } } /** * Decrease the count of remaining steps */ protected void beforeLog() { stepsToGo--; } /** * @param sensor * {@link Types} */ public void removeRegisteredGeo(Types sensor) { listeners.remove(sensor); listenersL.remove(sensor); listenersF.remove(sensor); listenersAges.remove(sensor); listLimits.remove(sensor); } /** * @param geo * {@link GeoElement} */ public void removeRegisteredGeo(GeoElement geo) { Types typeToRemove = null; if (geo instanceof GeoNumeric) { for (Entry<Types, GeoNumeric> entry : this.listeners.entrySet()) { Types type = entry.getKey(); if (entry.getValue() == geo) { typeToRemove = type; } } } else if (geo instanceof GeoList) { for (Entry<Types, GeoList> entry : this.listenersL.entrySet()) { Types type = entry.getKey(); if (entry.getValue() == geo) { typeToRemove = type; } } } else if (geo instanceof GeoFunction) { for (Entry<Types, GeoFunction> entry : this.listenersF.entrySet()) { Types type = entry.getKey(); if (entry.getValue() == geo) { typeToRemove = type; } } } if (typeToRemove != null) { removeRegisteredGeo(typeToRemove); } } private void prepareRegister(Types type, GeoElement geo, double limit) { Log.debug("logging " + type + " to " + geo.getLabelSimple()); listenersL.remove(type); listeners.remove(type); listenersF.remove(type); int lim = (int) Math.round(limit); if (lim < 0) { lim = 0; } listLimits.put(type, lim); listenersAges.put(type, 0); } public void registerGeoList(String s, GeoList list) { registerGeoList(s, list, 0); } public void registerGeoFunction(String s, GeoFunction list) { registerGeoFunction(s, list, 0); } public void registerGeoFunction(String s, GeoFunction function, double limit) { Types type = Types.lookup(s); if (type != null) { this.prepareRegister(type, function, limit); listenersF.put(type, function); } } public void registerGeoList(String s, GeoList list, double limit) { Types type = Types.lookup(s); if (type != null) { this.prepareRegister(type, list, limit); listenersL.put(type, list); } } public void stopLogging() { kernel.setUndoActive(oldUndoActive); kernel.storeUndoInfo(); closeSocket(); listeners.clear(); listenersL.clear(); listenersF.clear(); listenersAges.clear(); } protected void initStartLogging() { Log.debug( "startLogging called, undoActive is: " + kernel.isUndoActive()); // make sure that running StartLogging twice does not switch undo off oldUndoActive = oldUndoActive || kernel.isUndoActive(); kernel.setUndoActive(false); Log.debug("undoActive is: " + kernel.isUndoActive()); } protected void log(Types type, double timestamp, double val) { log(type, timestamp, val, true, true, true); } protected void log(Types type, double timestamp, double val, boolean repaint, boolean update, boolean atleast) { if (stepsToGo <= 0) { return; } GeoNumeric geo = listeners.get(type); if (geo != null) { // if (repaint) // If we do not want to repaint, probably logging // should be avoided as well... geo.setValue(val); if (repaint) { geo.updateRepaint(); } else if (update || !atleast) { geo.updateCascade(); } else { geo.update(); // at least call updateScripts } registerLog(type); } else { GeoList list = listenersL.get(type); if (list != null) { geo = new GeoNumeric(list.getConstruction(), val); Integer ll = listLimits.get(type); if (ll == null || ll == 0 || ll > list.size()) { list.add(geo); } else { list.addQueue(geo); } if (repaint) { list.updateRepaint(); } else if (update || !atleast) { list.updateCascade(); } else { list.update(); // at least call updateScripts } registerLog(type); } else { GeoFunction fn = listenersF.get(type); if (fn == null) { return; } ExpressionValue ev = fn.getFunctionExpression().unwrap().wrap() .getRight(); if (ev instanceof MyNumberPair && ((MyNumberPair) ev).getX() instanceof MyList && ((MyNumberPair) ev).getY() instanceof MyList) { MyList mx = (MyList) ((MyNumberPair) ev).getX(); MyList my = (MyList) ((MyNumberPair) ev).getY(); Integer ll = listLimits.get(type); if (mx.size() > 0 && timestamp < mx .getListElement(mx.size() - 1).evaluateDouble()) { mx.clear(); my.clear(); } if (ll == null || ll == 0 || ll + 2 > mx.size()) { mx.addListElement(new MyDouble(kernel, timestamp)); my.addListElement(new MyDouble(kernel, val)); } else { mx.addQue(timestamp, 0); my.addQue(val, 0); } } if (repaint) { fn.updateRepaint(); } else if (update || !atleast) { fn.updateCascade(); } else { fn.update(); // at least call updateScripts } registerLog(type); } } } private void registerLog(Types type) { Types thistype; GeoNumeric geo; GeoList list; GeoFunction function; int referenceAge = listenersAges.get(type); listenersAges.put(type, referenceAge + 1); referenceAge = listenersAges.get(type); int numOld = 0; int numAll = 0; // ages grow, and too little ages have to keep pace Iterator<Entry<Types, Integer>> it = listenersAges.entrySet() .iterator(); while (it.hasNext()) { Entry<Types, Integer> entry = it.next(); thistype = entry.getKey(); Integer age = entry.getValue(); if (age > 100) { numOld++; } numAll++; if (referenceAge > age + 1) { // grow the intermediates as well listenersAges.put(thistype, age + 1); geo = listeners.get(thistype); if (geo != null) { geo.update(); } else { list = listenersL.get(thistype); if (list != null) { list.update(); } else { function = listenersF.get(thistype); if (function != null) { function.update(); } } } } } if (numOld == numAll) { // we can decrease the ages of all it = listenersAges.entrySet().iterator(); while (it.hasNext()) { Entry<Types, Integer> entry = it.next(); thistype = entry.getKey(); Integer age = entry.getValue(); listenersAges.put(thistype, age - 100); } } } public void setLimit(double limit) { this.stepsToGo = (int) limit; } }