package com.eveningoutpost.dexdrip.Models; import android.os.AsyncTask; import android.provider.BaseColumns; import com.eveningoutpost.dexdrip.G5Model.Transmitter; import com.eveningoutpost.dexdrip.GcmActivity; import com.eveningoutpost.dexdrip.Home; import com.eveningoutpost.dexdrip.Models.UserError.Log; import com.activeandroid.Model; import com.activeandroid.annotation.Column; import com.activeandroid.annotation.Table; import com.activeandroid.query.Select; import com.eveningoutpost.dexdrip.UtilityModels.Constants; import com.eveningoutpost.dexdrip.UtilityModels.Pref; import com.eveningoutpost.dexdrip.utils.CheckBridgeBattery; import com.eveningoutpost.dexdrip.utils.DexCollectionType; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.annotations.Expose; import com.google.gson.internal.bind.DateTypeAdapter; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Date; import java.util.List; import java.util.UUID; /** * Created by Emma Black on 11/6/14. */ @Table(name = "TransmitterData", id = BaseColumns._ID) public class TransmitterData extends Model { private final static String TAG = TransmitterData.class.getSimpleName(); @Expose @Column(name = "timestamp", index = true) public long timestamp; // TODO these should be int or long surely @Expose @Column(name = "raw_data") public double raw_data; @Expose @Column(name = "filtered_data") public double filtered_data; @Expose @Column(name = "sensor_battery_level") public int sensor_battery_level; @Expose @Column(name = "uuid", index = true) public String uuid; public static synchronized TransmitterData create(byte[] buffer, int len, Long timestamp) { if (len < 6) { return null; } final TransmitterData transmitterData = new TransmitterData(); try { if ((buffer[0] == 0x11 || buffer[0] == 0x15) && buffer[1] == 0x00) { //this is a dexbridge packet. Process accordingly. Log.i(TAG, "create Processing a Dexbridge packet"); final ByteBuffer txData = ByteBuffer.allocate(len); txData.order(ByteOrder.LITTLE_ENDIAN); txData.put(buffer, 0, len); transmitterData.raw_data = txData.getInt(2); transmitterData.filtered_data = txData.getInt(6); // bitwise and with 0xff (1111....1) to avoid that the byte is treated as signed. transmitterData.sensor_battery_level = txData.get(10) & 0xff; if (buffer[0] == 0x15) { Log.i(TAG, "create Processing a Dexbridge packet includes delay information"); transmitterData.timestamp = timestamp - txData.getInt(16); } else { transmitterData.timestamp = timestamp; } Log.i(TAG, "Created transmitterData record with Raw value of " + transmitterData.raw_data + " and Filtered value of " + transmitterData.filtered_data + " at " + timestamp + " with timestamp " + transmitterData.timestamp); } else { //this is NOT a dexbridge packet. Process accordingly. Log.i(TAG, "create Processing a BTWixel or IPWixel packet"); StringBuilder data_string = new StringBuilder(); for (int i = 0; i < len; ++i) { data_string.append((char) buffer[i]); } final String[] data = data_string.toString().split("\\s+"); if (data.length > 1) { transmitterData.sensor_battery_level = Integer.parseInt(data[1]); if (data.length > 2) { try { Pref.setInt("bridge_battery", Integer.parseInt(data[2])); if (Home.get_master()) { GcmActivity.sendBridgeBattery(Pref.getInt("bridge_battery", -1)); } CheckBridgeBattery.checkBridgeBattery(); } catch (Exception e) { Log.e(TAG, "Got exception processing classic wixel or limitter battery value: " + e.toString()); } if (data.length > 3) { if ((DexCollectionType.getDexCollectionType() == DexCollectionType.LimiTTer) && (!Pref.getBooleanDefaultFalse("use_transmiter_pl_bluetooth"))) { try { // reported sensor age in minutes final Integer sensorAge = Integer.parseInt(data[3]); if ((sensorAge > 0) && (sensorAge < 200000)) Pref.setInt("nfc_sensor_age", sensorAge); } catch (Exception e) { Log.e(TAG, "Got exception processing field 4 in classic limitter protocol: " + e); } } } } } transmitterData.raw_data = Integer.parseInt(data[0]); transmitterData.filtered_data = Integer.parseInt(data[0]); // TODO process does_have_filtered_here with extended protocol transmitterData.timestamp = timestamp; } //Stop allowing readings that are older than the last one - or duplicate data, its bad! (from savek-cc) final TransmitterData lastTransmitterData = TransmitterData.last(); if (lastTransmitterData != null && lastTransmitterData.timestamp >= transmitterData.timestamp) { Log.e(TAG, "Rejecting TransmitterData constraint: last: " + JoH.dateTimeText(lastTransmitterData.timestamp) + " >= this: " + JoH.dateTimeText(transmitterData.timestamp)); return null; } if (lastTransmitterData != null && lastTransmitterData.raw_data == transmitterData.raw_data && Math.abs(lastTransmitterData.timestamp - transmitterData.timestamp) < (Constants.MINUTE_IN_MS * 2)) { Log.e(TAG, "Rejecting identical TransmitterData constraint: last: " + JoH.dateTimeText(lastTransmitterData.timestamp) + " due to 2 minute rule this: " + JoH.dateTimeText(transmitterData.timestamp)); return null; } final Calibration lastCalibration = Calibration.lastValid(); if (lastCalibration != null && lastCalibration.timestamp > transmitterData.timestamp) { Log.e(TAG, "Rejecting historical TransmitterData constraint: calib: " + JoH.dateTimeText(lastCalibration.timestamp) + " > this: " + JoH.dateTimeText(transmitterData.timestamp)); return null; } transmitterData.uuid = UUID.randomUUID().toString(); transmitterData.save(); return transmitterData; }catch(Exception e) { Log.e(TAG, "Got exception processing fields in protocol: " + e); } return null; } public static synchronized TransmitterData create(int raw_data, int filtered_data, int sensor_battery_level, long timestamp) { TransmitterData lastTransmitterData = TransmitterData.last(); if (lastTransmitterData != null && lastTransmitterData.raw_data == raw_data && Math.abs(lastTransmitterData.timestamp - new Date().getTime()) < (Constants.MINUTE_IN_MS * 2)) { //Stop allowing duplicate data, its bad! return null; } TransmitterData transmitterData = new TransmitterData(); transmitterData.sensor_battery_level = sensor_battery_level; transmitterData.raw_data = raw_data; transmitterData.filtered_data = filtered_data; transmitterData.timestamp = timestamp; transmitterData.uuid = UUID.randomUUID().toString(); transmitterData.save(); if (JoH.areWeRunningOnAndroidWear()) { saveWearBattery(); } return transmitterData; } private static void saveWearBattery() { int wearBatteryLevel = CheckBridgeBattery.getBatteryLevel(Home.getAppContext()); Log.i(TAG, "create wearBatteryLevel=" + wearBatteryLevel); Pref.setInt("bridge_battery", wearBatteryLevel);//TODO confirm wear battery should be used as bridge CheckBridgeBattery.checkBridgeBattery(); } public static synchronized TransmitterData create(int raw_data ,int sensor_battery_level, long timestamp) { TransmitterData lastTransmitterData = TransmitterData.last(); if (lastTransmitterData != null && lastTransmitterData.raw_data == raw_data && Math.abs(lastTransmitterData.timestamp - new Date().getTime()) < (Constants.MINUTE_IN_MS * 2)) { //Stop allowing duplicate data, its bad! return null; } TransmitterData transmitterData = new TransmitterData(); transmitterData.sensor_battery_level = sensor_battery_level; transmitterData.raw_data = raw_data ; transmitterData.timestamp = timestamp; transmitterData.uuid = UUID.randomUUID().toString(); transmitterData.save(); return transmitterData; } public static TransmitterData last() { return new Select() .from(TransmitterData.class) .orderBy("_ID desc") .executeSingle(); } public static List<TransmitterData> latestForGraphAsc(int number, long startTime) {//KS return latestForGraphAsc(number, startTime, Long.MAX_VALUE); } public static List<TransmitterData> latestForGraphAsc(int number, long startTime, long endTime) {//KS return new Select() .from(TransmitterData.class) .where("timestamp >= " + Math.max(startTime, 0)) .where("timestamp <= " + endTime) //.where("calculated_value != 0") .where("raw_data != 0") .orderBy("timestamp asc") .limit(number) .execute(); } public static List<TransmitterData> last(int count) { return new Select() .from(TransmitterData.class) .orderBy("_ID desc") .limit(count) .execute(); } public static TransmitterData lastByTimestamp() { return new Select() .from(TransmitterData.class) .orderBy("timestamp desc") .executeSingle(); } public static TransmitterData getForTimestamp(double timestamp) {//KS try { Sensor sensor = Sensor.currentSensor(); if (sensor != null) { TransmitterData bgReading = new Select() .from(TransmitterData.class) .where("timestamp <= ?", (timestamp + (60 * 1000))) // 1 minute padding (should never be that far off, but why not) .orderBy("timestamp desc") .executeSingle(); if (bgReading != null && Math.abs(bgReading.timestamp - timestamp) < (3 * 60 * 1000)) { //cool, so was it actually within 4 minutes of that bg reading? Log.i(TAG, "getForTimestamp: Found a BG timestamp match"); return bgReading; } } } catch (Exception e) { Log.e(TAG,"getForTimestamp() Got exception on Select : "+e.toString()); return null; } Log.d(TAG, "getForTimestamp: No luck finding a BG timestamp match"); return null; } public static void cleanup(long timestamp) { List<TransmitterData> transmitterData = new Select() .from(TransmitterData.class) .where("timestamp < ?", timestamp) .orderBy("timestamp desc") .execute(); if (transmitterData != null) Log.d(TAG, "cleanup TransmitterData size=" + transmitterData.size()); new Cleanup().execute(transmitterData); } public static TransmitterData findByUuid(String uuid) {//KS try { return new Select() .from(TransmitterData.class) .where("uuid = ?", uuid) .executeSingle(); } catch (Exception e) { Log.e(TAG,"findByUuid() Got exception on Select : "+e.toString()); return null; } } public String toS() {//KS Gson gson = new GsonBuilder() .excludeFieldsWithoutExposeAnnotation() .registerTypeAdapter(Date.class, new DateTypeAdapter()) .serializeSpecialFloatingPointValues() .create(); return gson.toJson(this); } private static class Cleanup extends AsyncTask<List<TransmitterData>, Integer, Boolean> { @Override protected Boolean doInBackground(List<TransmitterData>... errors) { try { for(TransmitterData transmitterData : errors[0]) { transmitterData.delete(); } return true; } catch(Exception e) { return false; } } } public static void updateTransmitterBatteryFromSync(final int battery_level) { try { TransmitterData td = TransmitterData.last(); if ((td == null) || (td.raw_data!=0)) { td=TransmitterData.create(0,battery_level,(long)JoH.ts()); Log.d(TAG,"Created new fake transmitter data record for battery sync"); if (td==null) return; } if ((battery_level != td.sensor_battery_level) || ((JoH.ts()-td.timestamp)>(1000*60*60))) { td.sensor_battery_level = battery_level; td.timestamp = (long)JoH.ts(); // freshen timestamp on this bogus record for system status Log.d(TAG,"Saving synced sensor battery, new level: "+battery_level); td.save(); } else { Log.d(TAG,"Synced sensor battery level same as existing: "+battery_level); } } catch (Exception e) { Log.e(TAG,"Got exception updating sensor battery from sync: "+e.toString()); } } private static double roundRaw(TransmitterData td) { return JoH.roundDouble(td.raw_data,3); } private static double roundFiltered(TransmitterData td) { return JoH.roundDouble(td.filtered_data,3); } public static boolean unchangedRaw() { final List<TransmitterData> items = last(3); if (items != null && items.size() == 3) { return (roundRaw(items.get(0)) == roundRaw(items.get(1)) && roundRaw(items.get(0)) == roundRaw(items.get(2)) && roundFiltered(items.get(0)) == roundFiltered(items.get(1)) && roundFiltered(items.get(0)) == roundFiltered(items.get(2))); } return false; } }