package com.eveningoutpost.dexdrip.Models; import com.eveningoutpost.dexdrip.ImportedLibraries.usbserial.util.HexDump; import com.eveningoutpost.dexdrip.Models.UserError.Log; import com.eveningoutpost.dexdrip.NFCReaderX; import com.eveningoutpost.dexdrip.R; import com.eveningoutpost.dexdrip.UtilityModels.Blukon; import com.eveningoutpost.dexdrip.UtilityModels.BridgeResponse; import com.eveningoutpost.dexdrip.UtilityModels.LibreUtils; import com.eveningoutpost.dexdrip.UtilityModels.PersistentStore; import com.eveningoutpost.dexdrip.UtilityModels.Pref; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import static com.eveningoutpost.dexdrip.xdrip.gs; /** * Created by Tzachi Dar on 7.3.2018. */ public class Tomato { private static final String TAG = "DexCollectionService";//?????"Tomato"; private static final String CHECKSUM_FAILED = "checksum failed"; private static final String SERIAL_FAILED = "serial failed"; private enum TOMATO_STATES { REQUEST_DATA_SENT, RECIEVING_DATA } private final static int TOMATO_HEADER_LENGTH = 18; private final static int TOMATO_PATCH_INFO = 6; static volatile TOMATO_STATES s_state; private static volatile long s_lastReceiveTimestamp; private static volatile byte[] s_full_data = null; private static volatile int s_acumulatedSize = 0; private static volatile boolean s_recviedEnoughData; public static boolean isTomato() { final ActiveBluetoothDevice activeBluetoothDevice = ActiveBluetoothDevice.first(); if (activeBluetoothDevice == null || activeBluetoothDevice.name == null) { return false; } return activeBluetoothDevice.name.startsWith("miaomiao") || activeBluetoothDevice.name.toLowerCase().startsWith("watlaa"); } public static BridgeResponse decodeTomatoPacket(byte[] buffer, int len) { final BridgeResponse reply = new BridgeResponse(); // Check time, probably need to start on sending long now = JoH.tsl(); if(now - s_lastReceiveTimestamp > 3*1000) { // We did not receive data in 3 seconds, moving to init state again Log.e(TAG, "Recieved a buffer after " + (now - s_lastReceiveTimestamp) / 1000 + " seconds, starting again. "+ "already acumulated " + s_acumulatedSize + " bytes."); s_state = TOMATO_STATES.REQUEST_DATA_SENT; } s_lastReceiveTimestamp = now; if (buffer == null) { Log.e(TAG, "null buffer passed to decodeTomatoPacket"); return reply; } if (s_state == TOMATO_STATES.REQUEST_DATA_SENT) { if(buffer.length == 1 && buffer[0] == 0x32) { Log.e(TAG, "returning allow sensor confirm"); ByteBuffer allowNewSensor = ByteBuffer.allocate(2); allowNewSensor.put(0, (byte) 0xD3); allowNewSensor.put(1, (byte) 0x01); reply.add(allowNewSensor); // For debug, make it send data every minute (did not work...) ByteBuffer newFreqMessage = ByteBuffer.allocate(2); newFreqMessage.put(0, (byte) 0xD1); newFreqMessage.put(1, (byte) 0x05); reply.add(newFreqMessage); //command to start reading ByteBuffer ackMessage = ByteBuffer.allocate(1); ackMessage.put(0, (byte) 0xF0); reply.add(ackMessage); return reply; } if(buffer.length == 1 && buffer[0] == 0x34) { Log.e(TAG, "No sensor has been found"); reply.setError_message(gs(R.string.no_sensor_found)); return reply; } // 18 is the expected header size if(buffer.length >= TOMATO_HEADER_LENGTH && buffer[0] == 0x28) { // We are starting to receive data, need to start accumulating // &0xff is needed to convert to hex. int expectedSize = 256 * (int)(buffer[1] & 0xFF) + (int)(buffer[2] & 0xFF); Log.e(TAG, "Starting to acumulate data expectedSize = " + expectedSize); InitBuffer(expectedSize + TOMATO_PATCH_INFO); addData(buffer); s_state = TOMATO_STATES.RECIEVING_DATA; return reply; } else { if (JoH.quietratelimit("unknown-initial-packet", 1)) { Log.d(TAG,"Unknown initial packet makeup received" + HexDump.dumpHexString(buffer)); } return reply; } } if (s_state == TOMATO_STATES.RECIEVING_DATA) { //Log.e(TAG, "received more data s_acumulatedSize = " + s_acumulatedSize + " current buffer size " + buffer.length); try { addData(buffer); } catch (RuntimeException e) { // if the checksum failed lets ask for the data set again but not more than once per minute if (e.getMessage().equals(CHECKSUM_FAILED)) { if (JoH.ratelimit("tomato-full-retry",60) || JoH.ratelimit("tomato-full-retry2",60)) { reply.getSend().clear(); reply.getSend().addAll(Tomato.resetTomatoState()); reply.setDelay(8000); reply.setError_message(gs(R.string.checksum_failed__retrying)); Log.d(TAG,"Asking for retry of data"); } } else if (e.getMessage().equals(SERIAL_FAILED)) { reply.setError_message("Sensor Serial Problem"); } else throw e; } return reply; } Log.wtf(TAG, "Very strange, In an unexpected state " + s_state); return reply; } static void addData(byte[] buffer) { if(s_acumulatedSize + buffer.length > s_full_data.length) { Log.e(TAG, "Error recieving too much data. exiting. s_acumulatedSize = " + s_acumulatedSize + " buffer.length = " + buffer.length + " s_full_data.length " + s_full_data.length); //??? send something to start back?? return; } System.arraycopy(buffer, 0, s_full_data, s_acumulatedSize, buffer.length); s_acumulatedSize += buffer.length; AreWeDone(); } static void AreWeDone() { // Give both versions a chance to work. final int extended_length = 344 + TOMATO_HEADER_LENGTH + 1 + TOMATO_PATCH_INFO; if(s_recviedEnoughData && (s_acumulatedSize != extended_length)) { // This reading already ended Log.e(TAG,"Getting out, as s_recviedEnoughData and we have too much data already s_acumulatedSize = " + s_acumulatedSize); return; } if(s_acumulatedSize < 344 + TOMATO_HEADER_LENGTH + 1 ) { //Log.e(TAG,"Getting out, since not enough data s_acumulatedSize = " + s_acumulatedSize); return; } byte[] data = Arrays.copyOfRange(s_full_data, TOMATO_HEADER_LENGTH, TOMATO_HEADER_LENGTH+344); s_recviedEnoughData = true; long now = JoH.tsl(); // Important note, the actual serial number is 8 bytes long and starts at addresses 5. final String SensorSn = LibreUtils.decodeSerialNumberKey(Arrays.copyOfRange(s_full_data, 5, 13)); byte []patchUid = null; byte []patchInfo = null; if(s_acumulatedSize >= extended_length) { patchUid = Arrays.copyOfRange(s_full_data, 5, 13); patchInfo = Arrays.copyOfRange(s_full_data, TOMATO_HEADER_LENGTH+ 344 + 1 , TOMATO_HEADER_LENGTH + 344 + 1+ TOMATO_PATCH_INFO); } Log.d(TAG, "patchUid = " + HexDump.dumpHexString(patchUid)); Log.d(TAG, "patchInfo = " + HexDump.dumpHexString(patchInfo)); boolean checksum_ok = NFCReaderX.HandleGoodReading(SensorSn, data, now, true, patchUid, patchInfo); Log.e(TAG, "We have all the data that we need " + s_acumulatedSize + " checksum_ok = " + checksum_ok + HexDump.dumpHexString(data)); if(!checksum_ok) { throw new RuntimeException(CHECKSUM_FAILED); } if (SensorSanity.checkLibreSensorChangeIfEnabled(SensorSn)) { Log.e(TAG,"Problem with Libre Serial Number - not processing"); throw new RuntimeException(SERIAL_FAILED); } PersistentStore.setString("Tomatobattery", Integer.toString(s_full_data[13])); Pref.setInt("bridge_battery", s_full_data[13]); PersistentStore.setString("TomatoHArdware",HexDump.toHexString(s_full_data,16,2)); PersistentStore.setString("TomatoFirmware",HexDump.toHexString(s_full_data,14,2)); PersistentStore.setString("LibreSN", SensorSn); } // This is the function that we should have once we are able to read all data realiably. static void AreWeDoneMax() { if(s_acumulatedSize == s_full_data.length) { Log.e(TAG, "We have a full packet"); } else { return; } if(s_full_data[s_full_data.length -1] != 0x29) { Log.e(TAG, "recieved full data, but last byte is not 0x29. It is " + s_full_data[s_full_data.length -1]); return; } // We have all the data if(s_full_data.length < 344 + TOMATO_HEADER_LENGTH + 1) { Log.e(TAG, "We have all the data, but it is not enough... s_full_data.length = " + s_full_data.length ); return; } Log.e(TAG, "We have a full packet"); } static void InitBuffer(int expectedSize) { s_full_data = new byte[expectedSize]; s_acumulatedSize = 0; s_recviedEnoughData = false; } public static ArrayList initialize() { Log.i(TAG, "initialize!"); Pref.setInt("bridge_battery", 0); //force battery to no-value before first reading return resetTomatoState(); } private static ArrayList resetTomatoState() { ArrayList ret = new ArrayList<>(); s_state = TOMATO_STATES.REQUEST_DATA_SENT; // Make tomato send data every 5 minutes ByteBuffer newFreqMessage = ByteBuffer.allocate(2); newFreqMessage.put(0, (byte) 0xD1); newFreqMessage.put(1, (byte) 0x05); ret.add(newFreqMessage); //command to start reading ByteBuffer ackMessage = ByteBuffer.allocate(1); ackMessage.put(0, (byte) 0xF0); ret.add(ackMessage); return ret; } }