Java tutorial
package core.module.codec; import java.io.*; import java.security.InvalidKeyException; import java.util.Calendar; import java.util.Date; import java.util.logging.Level; import java.util.logging.Logger; import helper.lang.IntegerHelper; import core.medicaldevice.MedicalDevice; import core.datapoint.DataPoint; import helper.util.DateHelper; import org.apache.commons.codec.binary.Base64; /** * Confidential Information. * Copyright (C) 2003, 2004, 2005, 2006 Eric Link, All rights reserved. * PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. **/ public class EncodeDecodeOtaMessage { private static Logger logger = Logger.getLogger(EncodeDecodeOtaMessage.class.getName()); // PARAMETER private final static int SET_PARAMETER = 0x10; private final static int SET_PARAMETER_ACK = 0x1A; private final static int SET_PARAMETER_FAILED = 0x1F; private final static int GET_PARAMETER = 0x20; private final static int GET_PARAMETER_ACK = 0x2A; private final static int GET_PARAMETER_FAILED = 0x2F; private final static int PARAMETER_GET_ALL_PARAMETERS = 0x00; private final static int PARAMETER_OTA_TIME_SETTINGS = 0x10; private final static int PARAMETER_RADIO_SLEEP = 0x20; private final static int PARAMETER_ALARM_LIMIT = 0x30; private final static int PARAMETER_AUTODELETE = 0x40; private final static int PARAMETER_BATTERY_STATUS = 0x50; private final static int PARAMETER_SOFTWARE_VERSION = 0x51; private final static int PARAMETER_LED_STATE = 0x52; private final static int PARAMETER_MAIL_TO_ADDRESS = 0x60; private final static int PARAMETER_AUTO_SEND_NETWORK_INFO = 0x70; private final static int PARAMETER_NETWORK_INFO = 0x71; private final static int PARAMETER_ENABLE_FLEXSUITE_HEADER = 0x72; private final static int PARAMETER_MESSAGE_COUNTS = 0x73; private final static int PARAMETER_ENABLE_ENCRYPTION = 0x80; private final static int PARAMETER_ENCRYPTION_KEY = 0x81; private final static int PARAMETER_RESET_ENCRYPTION_KEY = 0x82; private final static int PARAMETER_SET_DEBUG_INFO = 0x90; private final static int PARAMETER_BATTERY_VOLTAGE_MV = 0x91; private final static int PARAMETER_DUTY_CYCLES = 0x92; private final static int PARAMETER_LAST_CHARGE_TIME = 0x93; private final static int PARAMETER_BATTERY_LOW = 0x94; private final static int PARAMETER_BATTERY_OFFSET = 0x95; private final static int PARAMETER_BATTERY_MAX = 0x96; // //0x91 battery Voltage ( in mV ) 0x0FA0 = 4000mV //0x92 duty cycles byte1 = trickle charge; byte2 = fast charge //0x93 battery last charge time ( 3 byte ) // LastChargeTime = Date19bit(&Time, &Date); // ERASE private final static int ERASE_COMMAND = 0x30; private final static int ERASE_COMMAND_ACK = 0x3A; private final static int ERASE_COMMAND_FAILED = 0x3F; private final static int ERASE_ALL_READINGS = 0x10; //Erase all readings, no [SN] needed. [SN] is not deleted private final static int ERASE_ALL_READINGS_FOR_SN = 0x11; //Erase all readings of specified [SN]. [SN] is not deleted private final static int ERASE_ALL_DELIVERED_READINGS = 0x20; //Erase all delivered readings, no [SN] needed. [SN] is kept private final static int ERASE_ALL_DELIVERED_READINGS_FOR_SN = 0x21; //Erase all delivered readings of specified [SN]. [SN] is not deleted private final static int ERASE_ALL_SN_ALL_ENTRIES = 0x30; //Erase all [SN] and all entries private final static int ERASE_ALL_ENTRIES_FOR_SN = 0x31; //Erase [SN] and all entries of specific [SN] // RETRIEVE private final static int RETRIEVE_COMMAND = 0x40; private final static int RETRIEVE_COMMAND_RESPONSE = 0x4A; private final static int RETRIEVE_COMMAND_RESPONSE_FAILED = 0x4F; private final static int RETRIEVE_ALL_READINGS = 0x10; private final static int RETRIEVE_ALL_READINGS_FOR_SN = 0x11; private final static int RETRIEVE_UNDELIVERED_READINGS = 0x20; private final static int RETRIEVE_UNDELIVERED_READINGS_FOR_SN = 0x21; private final static int RETRIEVE_SN_LIST = 0x30; private final static int SEND_READING_NORMAL_RESPONSE = 0x5A; private final static int SEND_READING_ALARM_RESPONSE = 0x5F; private final static int TIME_CORRECTION_READING_FLAG_VALUE = 1020; private final static int CT_MASK = 0x0800; //Control Test bit flag private final static int TC_MASK = 0x0400; //Time Correct bit flag private final static int TC_VALUE_MASK = 0x03FC; //1020 in reading value for a tc (2 flags?) private final static int READING_VALUE_MASK = 0x03FF; // 10 bit reading from 2 bytes private final static int GET_DIAGNOSTIC_COMMAND = 0x60; // EVENT private final static int EVENT_READ_GM = 0xF0; private final static int EVENT_BATTERY_LOW = 0xF1; private final static int EVENT_BATTERY_FULL = 0xF2; private final static int EVENT_MAX_SN_EXCEEDED = 0xF3; private final static int EVENT_CORRUPTED_GM_CONNECTED = 0xF4; private final static int EVENT_RADIO_RESET = 0xF6; private final static int EVENT_DELETE_PENDING_COMMANDS = 0xFF; // FAILED private final static int FAILED_INVALID_COMMAND = 0x01; private final static int FAILED_INVALID_PARAMETER_ID = 0x02; private final static int FAILED_INVALID_PARAMETER_VALUE = 0x03; private final static int FAILED_INVALID_COMMAND_LENGTH = 0x04; private final static int FAILED_PARAMETER_NOT_CHANGEABLE = 0x05; private final static int FAILED_ERROR_NOT_SPECIFIED = 0x06; // INVALID private final static int INVALID_MSG_CONTENT = 0x0F; private EncodeDecodeOtaMessage() { } static { DateHelper.setDefaultTimeZone(); } /*################# Helper Methods ##################*/ public static String toHexString(int b) { String hexString = Integer.toHexString(b); if (hexString.length() == 1) { hexString = "0" + hexString; } return hexString.toUpperCase(); } public static String toHexString(byte b) { String hexString = Integer.toHexString((byte) b); if (hexString.length() == 1) { hexString = "0" + hexString; } else if (hexString.length() > 2) { hexString = hexString.substring(hexString.length() - 2, hexString.length()); } return hexString.toUpperCase(); } public static String toHexString(byte[] b) { String hexString = ""; for (int i = 0; i < b.length; i++) { hexString += toHexString(b[i]); } return hexString.toUpperCase(); } public static String toHexString(int[] b) { String hexString = ""; for (int i = 0; i < b.length; i++) { hexString += toHexString(b[i]); } return hexString.toUpperCase(); } public static String toIntString(int[] b) { String intString = ""; for (int i = 0; i < b.length; i++) { intString += Integer.valueOf(String.valueOf(b[i])) + ","; } return intString; } public static String toCharString(int[] b) { String charString = ""; for (int i = 0; i < b.length; i++) { charString += String.valueOf((char) b[i]); } return charString; } private static boolean isEncryptedOutboundMessage(int[] message) { return (message[0] == SET_PARAMETER && message[2] == PARAMETER_ENCRYPTION_KEY) || (message[0] == RETRIEVE_COMMAND && message[2] == RETRIEVE_ALL_READINGS_FOR_SN) || (message[0] == RETRIEVE_COMMAND && message[2] == RETRIEVE_UNDELIVERED_READINGS_FOR_SN) || (message[0] == ERASE_COMMAND && message[2] == ERASE_ALL_ENTRIES_FOR_SN) || (message[0] == ERASE_COMMAND && message[2] == ERASE_ALL_READINGS_FOR_SN) || (message[0] == ERASE_COMMAND && message[2] == ERASE_ALL_DELIVERED_READINGS_FOR_SN); } private static boolean isEncryptedInboundMessage(byte[] message) { return (message[0] == (byte) SEND_READING_NORMAL_RESPONSE) || (message[0] == (byte) SEND_READING_ALARM_RESPONSE) || (message[0] == (byte) RETRIEVE_COMMAND_RESPONSE && message.length > 2) //only encrypted if serial numbers are in the list || (message[0] == (byte) GET_PARAMETER_ACK && message[2] == (byte) PARAMETER_ENCRYPTION_KEY) //all parms is not encrypted.|| //(message[0] == (byte)GET_PARAMETER_ACK && message[2] == (byte)PARAMETER_GET_ALL_PARAMETERS) ; } public static byte[] encryptOtaMessage(GlucoMon glucoMon, int[] encodedOtaMessage) throws InvalidKeyException { byte[] encryptedMessage; if (glucoMon.isEncrypted() && isEncryptedOutboundMessage(encodedOtaMessage)) { logger.finer(new String(glucoMon.getSecretKey())); logger.finer("Encrypt message"); // convert the int array to a byte array. yuck. int messageLength = encodedOtaMessage.length; byte[] encodedOtaMessageByteArray = new byte[messageLength]; for (int i = 0; i < messageLength; i++) { encodedOtaMessageByteArray[i] = (byte) encodedOtaMessage[i]; } encryptedMessage = null; byte command = encodedOtaMessageByteArray[0]; //int originalLength = encodedOtaMessage[1]; byte parameter = encodedOtaMessageByteArray[2]; int offset = 3; //always three with current commands // pad and encrypt the payload portion of the message byte[] plainText = new byte[encodedOtaMessageByteArray.length - offset]; System.arraycopy(encodedOtaMessageByteArray, offset, plainText, 0, plainText.length); Crypt crypt = new Crypt(); byte[] cipherText = crypt.encrypt(plainText, glucoMon.getSecretKey()); // put it back together into the encrypted message array encryptedMessage = new byte[cipherText.length + offset]; encryptedMessage[0] = command; encryptedMessage[1] = (byte) (cipherText.length + 1); //length include parameter and payload. encryptedMessage[2] = parameter; System.arraycopy(cipherText, 0, encryptedMessage, offset, cipherText.length); } else { // convert the int array to a byte array. yuck. int messageLength = encodedOtaMessage.length; encryptedMessage = new byte[messageLength]; for (int i = 0; i < messageLength; i++) { encryptedMessage[i] = (byte) encodedOtaMessage[i]; } } logger.finer("Encrypted message bytes=\n" + IntegerHelper.toHexString(encryptedMessage)); // try { // FileOutputStream out = new FileOutputStream( "/test.bin" ); // out.write( encryptedMessage ); // out.flush(); // out.close(); // } catch ( Exception e ) {} return encryptedMessage; } public static InboundMessage decodeOtaMessage(GlucoMon glucoMon, Date messageSubmitDate, InputStream is) throws IOException, InvalidKeyException { InputStream clearTextInputStream; boolean is19BitDateFormat = false;//remove dependency on /etc/opt/diabetech/... these are not in production any more and this is safest way to modify code (e.g. don't modify it much) GlucoMonTimeStampFormatUtil.is19BitDateFormat( glucoMon ); //2A 11 81 FF4EB3AD 54A5E14A ECB2108B 0E0A6580 int messageId = is19BitDateFormat ? 0 : is.read(); logger.finer("messageId=" + messageId); if (glucoMon.isEncrypted()) { logger.finer("Decrypt message"); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int b = 0; while ((b = is.read()) != -1) { baos.write(b); } byte[] cipherText = baos.toByteArray(); if (isEncryptedInboundMessage(cipherText)) { byte command = cipherText[0]; byte length = cipherText[1]; byte parameter = cipherText[2]; // based on which command and parameter combination; set offset to 2 or 3. int offset = 0; switch (command) { case (byte) RETRIEVE_COMMAND_RESPONSE: case (byte) SEND_READING_NORMAL_RESPONSE: case (byte) SEND_READING_ALARM_RESPONSE: // subtract message id for new format offset = is19BitDateFormat ? 2 : 1; break; case (byte) GET_PARAMETER_ACK: // subtract message id for new format // parms all working ok w/ 3 offset // offset = is19BitDateFormat ? 3 : 2; offset = 3; break; default: throw new IllegalArgumentException("Incorrectly set offset in decryption routine"); } byte[] payload = new byte[cipherText.length - offset]; System.arraycopy(cipherText, offset, payload, 0, payload.length); // put together in cleartext message array Crypt crypt = new Crypt(); byte[] plainTextPayload = crypt.decrypt(payload, glucoMon.getSecretKey()); logger.log(Level.FINER, "plainTextPayload={0}\npayload={1}\ncipherText={2}", new Object[] { plainTextPayload.length, payload.length, cipherText.length }); // copy plaintext payload back in to payload array. System.arraycopy(plainTextPayload, 0, cipherText, offset, plainTextPayload.length); clearTextInputStream = new ByteArrayInputStream(cipherText); } else { logger.finer("Encrypted glucoMon, message type is cleartext"); clearTextInputStream = new ByteArrayInputStream(cipherText); } } else { logger.finer("glucoMon is NOT encrypted"); clearTextInputStream = is; } // once it is decrypted, decode as normal. return decode(messageSubmitDate, clearTextInputStream, is19BitDateFormat); } private static int[] encryptSerialNumber(String serialNumber) { if (serialNumber.length() != 9) { throw new IllegalArgumentException("A serialNumber must be exactly 9 characters " + serialNumber); } byte[] nineByte = serialNumber.getBytes(); int[] nineInt = new int[9]; for (int i = 0; i < 9; i++) { nineInt[i] = nineByte[i]; } int[] eightByte = new int[8]; int[] rotated = new int[8]; core.module.codec.EncodeDecodeOtaMessage.byte9to8(nineInt, eightByte); core.module.codec.EncodeDecodeOtaMessage.rotate(eightByte, rotated); return rotated; } private static String decryptSerialNumber(InputStream is) throws IOException { int[] serialNumber8ByteRotated = new int[8]; serialNumber8ByteRotated[0] = is.read(); serialNumber8ByteRotated[1] = is.read(); serialNumber8ByteRotated[2] = is.read(); serialNumber8ByteRotated[3] = is.read(); serialNumber8ByteRotated[4] = is.read(); serialNumber8ByteRotated[5] = is.read(); serialNumber8ByteRotated[6] = is.read(); serialNumber8ByteRotated[7] = is.read(); int[] eightByte = new int[8]; int[] nineByte = new int[9]; rotate(serialNumber8ByteRotated, eightByte); byte8to9(eightByte, nineByte); StringBuffer sb = new StringBuffer(); sb.append((char) nineByte[0]); sb.append((char) nineByte[1]); sb.append((char) nineByte[2]); sb.append((char) nineByte[3]); sb.append((char) nineByte[4]); sb.append((char) nineByte[5]); sb.append((char) nineByte[6]); sb.append((char) nineByte[7]); sb.append((char) nineByte[8]); return sb.toString(); } /*################# Advantra Helper Methods ##################*/ /** * Port of advantra function. * <code> * void Byted9_to_8 (char* pIn, char* pOut) * { * for(int i=1; i<8 ;++i) * { *pOut = *pIn++ <<i; *pOut++ += *pIn >> (7-i); * } *pOut = *pIn++ <<i; *pOut++ += *pIn <<1; *pOut=0; * } * </code> */ private static void byte9to8(int[] in, int[] out) { int i; for (i = 1; i < 8; i++) { out[i - 1] = (in[i - 1] << i); out[i - 1] += (in[i] >> (7 - i)); } out[7] = (in[7] << i); out[7] += (in[8] << 1); // out[8]=0; for (i = 0; i < out.length; i++) { out[i] = out[i] & 0x00ff; } } /** * Port of advantra function. * <code> * void Byted8_to_9 (char* pIn, char* pOut) * { * for(int i=1; i<8;++i) * { *pOut += (*pIn >> i) & 0xFF >> i; *pOut++ &= 0x7F; *pOut = *pIn++ << (7-i); * } *pOut++ &= 0x7F; *pOut = *pIn >> 1; *pOut++ &= 0x7F; *pOut = 0; * } * </code> */ private static void byte8to9(int[] in, int[] out) { int i = 1; for (; i < 8; i++) { out[i - 1] += (in[i - 1] >>> i) & 0xFF >>> i; out[i - 1] = out[i - 1] & 0x007F; out[i] = in[i - 1] << (7 - i); } out[7] = out[7] & 0x007F; out[8] = in[7] >>> 1; out[8] = out[8] & 0x007F; // out[9] = 0; // // out[0] += 0x2F; } /** * Port of advantra function. * <code> * void Rotate (char* pIn,char* pOut) * { * unsigned int Matrix [8][8]; * * for( int i=0; i<8;++i) * for( int j=0; j<8;++j) * { * if ( *(pIn+i) & (1<<j) ) * Matrix[i][j] = 1; * else * Matrix[i][j] = 0; * } * * for( int j=0; j<8; ++j) * { *(pOut+j) = 0; * for(i=0; i<8;++i) * { *(pOut+j) += Matrix[i][j] * (1<<i); * } * } *(pOut+8)=0; * } * </code> */ private static void rotate(byte[] in, int[] out) { int[][] Matrix = new int[8][8]; int i = 0; int j = 0; for (i = 0; i < 8; ++i) { for (j = 0; j < 8; ++j) { if ((in[i] & (1 << j)) != 0) { Matrix[i][j] = 1; } else { Matrix[i][j] = 0; } } } for (j = 0; j < 8; ++j) { out[j] = 0; for (i = 0; i < 8; ++i) { out[j] += Matrix[i][j] * (1 << i); } } // out[8] = 0; //shuffle to end int outLength = out.length; int offset = outLength - 8; for (i = outLength; i > offset; i--) { out[i - 1] = out[i - 1 - offset]; } for (i = 0; i < offset; i++) { out[i] = 0; } } private static void rotate(int[] in, int[] out) { int[][] Matrix = new int[8][8]; int i = 0; int j = 0; for (i = 0; i < 8; ++i) { for (j = 0; j < 8; ++j) { if ((in[i] & (1 << j)) != 0) { Matrix[i][j] = 1; } else { Matrix[i][j] = 0; } } } for (j = 0; j < 8; ++j) { out[j] = 0; for (i = 0; i < 8; ++i) { out[j] += Matrix[i][j] * (1 << i); } } // out[8] = 0; //shuffle to end int outLength = out.length; int offset = outLength - 8; for (i = outLength; i > offset; i--) { out[i - 1] = out[i - 1 - offset]; } for (i = 0; i < offset; i++) { out[i] = 0; } } /** * Port of advantra function. * <code> * int Date19bit(int month,int day,int hour,int minute) * { * switch (month) * { * case 12: // december * day+=30; * case 11: // november * day+=31; * case 10: // oktober * day+=30; * case 9: // september * day+=31; * case 8: // august * day+=31; * case 7: // july * day+=30; * case 6: // june * day+=31; * case 5: // may * day+=30; * case 4: // april * day+=31; * case 3: // march * day+=29; * case 2: // februari * day+=31; * case 1: // januari * // do nothing here * break; * default: * //ASSERT; * break; * } * hour += --day * 24; * minute += hour * 60; * minute -= hour /3; * return minute; * } * </code> */ // private static int encode19BitDate( int month, int day, int hour, int minute) { // switch (month) { // // falls through and adds days for previous month (for each month down to Jan.) // case 12: // december // day+=30; // case 11: // november // day+=31; // case 10: // oktober // day+=30; // case 9: // september // day+=31; // case 8: // august // day+=31; // case 7: // july // day+=30; // case 6: // june // day+=31; // case 5: // may // day+=30; // case 4: // april // day+=31; // case 3: // march // day+=29; // case 2: // februari // day+=31; // case 1: // januari // // do nothing here // break; // default: // //ASSERT; // break; // } // hour += --day * 24; // minute += hour * 60; // minute -= hour /3; // return minute; // } /** * Port of advantra function. * <code> * void ReDate19bit(int& minute,int& hour,int& day,int& month) * { * int conv1, conv2, conv3; * month = 0; * conv1 = minute / 180; // conversion factor * minute += conv1; * conv2 = minute / 180; * * if(conv1!= conv2) * { * minute += ( conv2 - conv1 ); // conversion mistake * conv3 = minute / 180; * if(conv3 != conv2) * minute += ( conv3 - conv2 ); // conversion mistake * } * * hour = minute / 60; * minute %= 60; // minutes correct * * if(!minute) * if ( !( hour % 3 ) ) * minute = 1; * * if( ( minute == 59 ) && ( ( hour % 3 ) == 2 ) ) // rounding up!! * { * minute = 0; * ++hour; * } * * day = hour / 24; * hour %= 24; // hours correct * * if( day < 31 ) // januari * month = 1; * else * day -= 31; * * if(!month) * if( day < 29 ) // februari * month = 2; * else * day -= 29; * * if(!month) * if( day < 31 ) // maart * month = 3; * else * day -= 31; * * if(!month ) * if( day < 30 ) // april * month = 4; * else * day -= 30; * * if( !month ) * if( day < 31 ) // mei * month = 5; * else * day -= 31; * * if( !month ) * if( day < 30 ) // juni * month = 6; * else * day -= 30; * * if( !month) * if(day < 31 ) // juli * month = 7; * else * day -= 31; * * if( !month ) * if( day < 31 ) // augustus * month = 8; * else * day -= 31; * * if( !month) * if(day < 30 ) // september * month = 9; * else * day -= 30; * * if( !month) * if(day < 31 ) // oktober * month = 10; * else * day -= 31; * * if( !month) * if( day < 30 ) // november * month = 11; * else * day -= 30; * * if( !month) * if( day < 31 ) // december * month = 12; * else * day -= 31; * * // if(!month) * // ASSERT; * ++day; * } * </code> */ private static long convert19BitDateMinutes(long minute) { long conv1, conv2, conv3; conv1 = minute / 180; // conversion factor minute += conv1; conv2 = minute / 180; if (conv1 != conv2) { minute += (conv2 - conv1); // conversion mistake conv3 = minute / 180; if (conv3 != conv2) { minute += (conv3 - conv2); // conversion mistake } } return minute; } public static Calendar decode19BitDate(long minute) { long hour; long day; long month; month = 0; minute = convert19BitDateMinutes(minute); hour = minute / 60; minute %= 60; // minutes correct if (0 == minute) { if (!(hour % 3 != 0)) { minute = 1; } } if ((minute == 59) && ((hour % 3) == 2)) // rounding up!! { minute = 0; ++hour; } day = hour / 24; hour %= 24; // hours correct if (day < 31) // januari { month = 1; } else { day -= 31; } if (0 == month) { if (day < 29) // februari { month = 2; } else { day -= 29; } } if (0 == month) { if (day < 31) // maart { month = 3; } else { day -= 31; } } if (0 == month) { if (day < 30) // april { month = 4; } else { day -= 30; } } if (0 == month) { if (day < 31) // mei { month = 5; } else { day -= 31; } } if (0 == month) { if (day < 30) // juni { month = 6; } else { day -= 30; } } if (0 == month) { if (day < 31) // juli { month = 7; } else { day -= 31; } } if (0 == month) { if (day < 31) // augustus { month = 8; } else { day -= 31; } } if (0 == month) { if (day < 30) // september { month = 9; } else { day -= 30; } } if (0 == month) { if (day < 31) // oktober { month = 10; } else { day -= 31; } } if (0 == month) { if (day < 30) // november { month = 11; } else { day -= 30; } } if (0 == month) { if (day < 31) // december { month = 12; } else { day -= 31; } } ++day; Calendar cal = Calendar.getInstance(); // if month > this month then year is last year // if month <= this month, then year is this year int currentYear = cal.get(cal.YEAR); if (month > cal.get(cal.MONTH) + 1) { currentYear--; } // FOR TESTING AGAINST FILE DATES... currentYear = 2004; cal.clear(); cal.set(currentYear, (int) (month - 1), (int) day, (int) hour, (int) minute, 0); //logger.finer( "mm/dd/yyyy hh:mm=" + month + "/" + day + "/" + currentYear + " " + hour + ":" + minute ); return cal; } /*################# ENCODE Methods ##################*/ public static int[] encodeParameterSetTimeSetting(boolean updateGlucoseMeterWithNetworkTime, int profileTimeZoneUtcOffsetMinutes) { /* bit * 7 Network Time update 0 = disable [OTA] Time updates of [GM] 1 = enable [OTA] Time updates of [GM] 6 Time correction sign 0 = behind UTC time 1 = ahead of UTC time 5 to 0 Time correction value Number of 30 minute intervals difference from UTC time */ int timeSettings = 0; if (updateGlucoseMeterWithNetworkTime) { timeSettings |= 0x80; // flip to 1000 0000 to enable update glucose meter with DMD time // only set the time offset if we are going to use OTA time if (profileTimeZoneUtcOffsetMinutes > 0) { timeSettings |= 0x40; // flip to 0100 0000 because minutes are ahead of UTC } else { profileTimeZoneUtcOffsetMinutes *= -1; //flip the sign so we can add this - number to the timeSetting int } timeSettings += profileTimeZoneUtcOffsetMinutes / 30; } return new int[] { SET_PARAMETER, 0x02, PARAMETER_OTA_TIME_SETTINGS, timeSettings }; } public static int[] encodeParameterGetTimeSetting() { return new int[] { GET_PARAMETER, 0x01, PARAMETER_OTA_TIME_SETTINGS }; } public static int[] encodeParameterSetEnableFlexSuiteHeader(boolean enableFlexSuiteHeader) { return new int[] { SET_PARAMETER, 0x02, PARAMETER_ENABLE_FLEXSUITE_HEADER, enableFlexSuiteHeader ? 1 : 0 }; } public static int[] encodeParameterGetEnableFlexSuiteHeader() { return new int[] { GET_PARAMETER, 0x01, PARAMETER_ENABLE_FLEXSUITE_HEADER }; } public static int[] encodeParameterSetMessageCounts(byte stored, byte sent, byte failed) { return new int[] { SET_PARAMETER, 0x04, PARAMETER_MESSAGE_COUNTS, stored, sent, failed }; } public static int[] encodeParameterGetMessageCounts() { return new int[] { GET_PARAMETER, 0x01, PARAMETER_MESSAGE_COUNTS }; } public static int[] encodeParameterSetEnableEncryption(boolean enableEncryption) { return new int[] { SET_PARAMETER, 0x02, PARAMETER_ENABLE_ENCRYPTION, enableEncryption ? 1 : 0 }; } public static int[] encodeParameterGetEnableEncryption() { return new int[] { GET_PARAMETER, 0x01, PARAMETER_ENABLE_ENCRYPTION }; } public static int[] encodeParameterSetEncryptionKey(byte[] secretKey) { if (secretKey.length != 16) { throw new IllegalArgumentException("secretKey must be 16 bytes (128bits) long"); } return new int[] { SET_PARAMETER, 0x11, PARAMETER_ENCRYPTION_KEY, secretKey[0], secretKey[1], secretKey[2], secretKey[3], secretKey[4], secretKey[5], secretKey[6], secretKey[7], secretKey[8], secretKey[9], secretKey[10], secretKey[11], secretKey[12], secretKey[13], secretKey[14], secretKey[15] }; } public static int[] encodeParameterGetEncryptionKey() { return new int[] { GET_PARAMETER, 0x01, PARAMETER_ENCRYPTION_KEY }; } public static int[] encodeParameterResetEncryptionKey() { return new int[] { SET_PARAMETER, 0x02, PARAMETER_RESET_ENCRYPTION_KEY, 0x01 }; } public static int[] encodeParameterSetRadioSleep(int startTimeMinutes, int endTimeMinutes) { return new int[] { SET_PARAMETER, 0x03, PARAMETER_RADIO_SLEEP, startTimeMinutes / 6, endTimeMinutes / 6 }; } public static int[] encodeParameterGetRadioSleep() { return new int[] { GET_PARAMETER, 0x01, PARAMETER_RADIO_SLEEP }; } public static int[] encodeParameterSetAlarmLimit(int lowLimit, int highLimit) { return new int[] { SET_PARAMETER, 0x03, PARAMETER_ALARM_LIMIT, lowLimit / 3, highLimit / 3 }; } public static int[] encodeParameterGetAlarmLimit() { return new int[] { GET_PARAMETER, 0x01, PARAMETER_ALARM_LIMIT }; } public static int[] encodeParameterSetAutoDeleteGlucoseMeter(boolean autoDeleteGlucoseMeter) { return new int[] { SET_PARAMETER, 0x02, PARAMETER_AUTODELETE, autoDeleteGlucoseMeter ? 1 : 0 }; } public static int[] encodeParameterGetAutoDeleteGlucoseMeter() { return new int[] { GET_PARAMETER, 0x01, PARAMETER_AUTODELETE }; } public static int[] encodeParameterGetBatteryStatus() { return new int[] { GET_PARAMETER, 0x01, PARAMETER_BATTERY_STATUS }; } public static int[] encodeParameterGetBatteryVoltage() { return new int[] { GET_PARAMETER, 0x01, PARAMETER_BATTERY_VOLTAGE_MV }; } public static int[] encodeParameterGetDutyCycles() { return new int[] { GET_PARAMETER, 0x01, PARAMETER_DUTY_CYCLES }; } public static int[] encodeParameterGetLastChargeTime() { return new int[] { GET_PARAMETER, 0x01, PARAMETER_LAST_CHARGE_TIME }; } // public static int[] encodeParameterGetBatteryLow() { // return new int[] { GET_PARAMETER, 0x01, PARAMETER_BATTERY_LOW }; // } public static int[] encodeParameterSetBatteryOffset(short offsetVoltage) { return new int[] { SET_PARAMETER, 0x03, PARAMETER_BATTERY_OFFSET, (offsetVoltage >>> 0x08) & 0xFF, offsetVoltage & 0xFF }; } // public static int[] encodeParameterGetBatteryOffset() { // return new int[] { GET_PARAMETER, 0x01, PARAMETER_BATTERY_OFFSET }; // } // public static int[] encodeParameterGetBatteryMax() { // return new int[] { GET_PARAMETER, 0x01, PARAMETER_BATTERY_MAX }; // } public static int[] encodeParameterGetSoftwareVersion() { return new int[] { GET_PARAMETER, 0x01, PARAMETER_SOFTWARE_VERSION }; } public static int[] encodeParameterSetMailToAddress(String eMailAddress) { byte[] emailBytes = eMailAddress.getBytes(); int eMailAddressLength = emailBytes.length; if (eMailAddressLength > 50) { throw new IllegalArgumentException("eMail address may not be over 50 bytes"); } int[] otaMessage = new int[eMailAddressLength + 3]; otaMessage[0] = SET_PARAMETER; otaMessage[1] = eMailAddressLength + 1; otaMessage[2] = PARAMETER_MAIL_TO_ADDRESS; for (int i = 0; i < eMailAddressLength; i++) { otaMessage[i + 3] = emailBytes[i]; } return otaMessage; } public static int[] encodeParameterGetMailToAddress() { return new int[] { GET_PARAMETER, 0x01, PARAMETER_MAIL_TO_ADDRESS }; } public static int[] encodeParameterSetAutoSendNetworkInfo(boolean autoSendNetoworkInfo) { return new int[] { SET_PARAMETER, 0x02, PARAMETER_AUTO_SEND_NETWORK_INFO, autoSendNetoworkInfo ? 1 : 0 }; } public static int[] encodeParameterGetAutoSendNetworkInfo() { return new int[] { GET_PARAMETER, 0x01, PARAMETER_AUTO_SEND_NETWORK_INFO }; } public static int[] encodeParameterGetNetworkInfo() { return new int[] { GET_PARAMETER, 0x01, PARAMETER_NETWORK_INFO }; } public static int[] encodeParameterSetDebugInfo(boolean isDebugOn) { return new int[] { SET_PARAMETER, 0x02, PARAMETER_SET_DEBUG_INFO, isDebugOn ? 1 : 0 }; } public static int[] encodeParameterGetDebugInfo() { return new int[] { GET_PARAMETER, 0x01, PARAMETER_SET_DEBUG_INFO }; } public static int[] encodeEraseGlucoMon(boolean onlyEraseDeliveredReadings) { /* *0x20 Erase all delivered readings, no [SN] needed. [SN] is kept *0x10 Erase all readings, no [SN] needed. [SN] is not deleted */ return new int[] { ERASE_COMMAND, 0x01, onlyEraseDeliveredReadings ? ERASE_ALL_DELIVERED_READINGS : ERASE_ALL_READINGS }; } public static int[] encodeEraseGlucoMon(boolean onlyEraseDeliveredReadings, String serialNumber) { int[] encryptedSerialNumber = encryptSerialNumber(serialNumber); /* *0x11 Erase all readings of specified [SN]. [SN] is not deleted *0x21 Erase all delivered readings of specified [SN]. [SN] is not deleted */ return new int[] { ERASE_COMMAND, 0x09, onlyEraseDeliveredReadings ? ERASE_ALL_DELIVERED_READINGS_FOR_SN : ERASE_ALL_READINGS_FOR_SN, encryptedSerialNumber[0], encryptedSerialNumber[1], encryptedSerialNumber[2], encryptedSerialNumber[3], encryptedSerialNumber[4], encryptedSerialNumber[5], encryptedSerialNumber[6], encryptedSerialNumber[7] }; } public static int[] encodeEraseGlucoMonAllSerialNumbersAndReadings() { /* *0x30 Erase all [SN] and all entries */ return new int[] { ERASE_COMMAND, 0x01, ERASE_ALL_SN_ALL_ENTRIES }; } public static int[] encodeEraseGlucoMonSerialNumberAndReadings(String serialNumber) { int[] encryptedSerialNumber = encryptSerialNumber(serialNumber); /* *0x31 Erase [SN] and all entries of specific [SN] */ return new int[] { ERASE_COMMAND, 0x09, ERASE_ALL_ENTRIES_FOR_SN, encryptedSerialNumber[0], encryptedSerialNumber[1], encryptedSerialNumber[2], encryptedSerialNumber[3], encryptedSerialNumber[4], encryptedSerialNumber[5], encryptedSerialNumber[6], encryptedSerialNumber[7] }; } public static int[] encodeGetReadings(boolean onlySendUndeliveredReadings, String serialNumber) { int retrieveParameter; if (serialNumber != null) { int[] encryptedSerialNumber = encryptSerialNumber(serialNumber); /* *0x31 Erase [SN] and all entries of specific [SN] */ return new int[] { RETRIEVE_COMMAND, 0x09, onlySendUndeliveredReadings ? RETRIEVE_UNDELIVERED_READINGS_FOR_SN : RETRIEVE_ALL_READINGS_FOR_SN, encryptedSerialNumber[0], encryptedSerialNumber[1], encryptedSerialNumber[2], encryptedSerialNumber[3], encryptedSerialNumber[4], encryptedSerialNumber[5], encryptedSerialNumber[6], encryptedSerialNumber[7] }; } else { return new int[] { RETRIEVE_COMMAND, 0x01, onlySendUndeliveredReadings ? RETRIEVE_UNDELIVERED_READINGS : RETRIEVE_ALL_READINGS }; } } public static int[] encodeGetGlucoseMeterSerialNumbers() { return new int[] { RETRIEVE_COMMAND, 0x01, RETRIEVE_SN_LIST }; } public static int[] encodeGetDiagnosticInfo(byte diagnosticIndex) { return new int[] { GET_DIAGNOSTIC_COMMAND, 0x01, diagnosticIndex }; } public static int[] encodeGetAllParameters() { return new int[] { GET_PARAMETER, 0x01, PARAMETER_GET_ALL_PARAMETERS }; } public static int[] encodeEventReadGlucoseMeter() { return new int[] { EVENT_READ_GM }; } public static int[] encodeEventDeletePendingCommands() { return new int[] { EVENT_DELETE_PENDING_COMMANDS }; } /*################# DECODE Methods ##################*/ //Table 5 [OTA] Commands and Responses //Code Command Code Response //0x10 Set Parameter 0x1A Set Parameter Ack // 0x1F Set_Parameter failed //0x20 Get Parameter 0x2A Get Parameter Ack // 0x2F Get_Parameter failed //0x30 Erase Command 0x3A Erase Ack // 0x3F Erase failed //0x40 Retrieve Command 0x4A Retrieve Response // 0x4F Retrieve Response failed // 0x5A Send_Reading Normal // 0x5F Send_Reading Alarm //0xFx Send Event 0xFx Send Event private static InboundMessage decode(Date messageSubmitDate, InputStream is, boolean is19BitDateFormat) throws IOException { int b = is.read(); switch (b) { case RETRIEVE_COMMAND_RESPONSE: case SEND_READING_NORMAL_RESPONSE: case SEND_READING_ALARM_RESPONSE: if (is19BitDateFormat) { return decodeRetrieveCommand19Bit(messageSubmitDate, b, is); } else { return decodeRetrieveCommand(messageSubmitDate, b, is); } case SET_PARAMETER_ACK: return decodeSetParamterAck(is); case GET_PARAMETER_ACK: return decodeGetParamterAck(is, 0, is19BitDateFormat); case EVENT_BATTERY_LOW: return new EventBatteryLow(messageSubmitDate); case EVENT_BATTERY_FULL: return new EventBatteryFull(messageSubmitDate); case EVENT_MAX_SN_EXCEEDED: return new EventMaxSerialNumberExceeded(messageSubmitDate); case EVENT_CORRUPTED_GM_CONNECTED: return new EventCorruptedGlucoseMeterConnected(messageSubmitDate); case EVENT_RADIO_RESET: return new EventRadioReset(messageSubmitDate); case ERASE_COMMAND_ACK: return decodeEraseCommandAck(is); case SET_PARAMETER_FAILED: return decodeSetParameterFailed(is); case GET_PARAMETER_FAILED: return decodeGetParameterFailed(is); case ERASE_COMMAND_FAILED: return decodeEraseCommandFailed(is); case RETRIEVE_COMMAND_RESPONSE_FAILED: return decodeRetrieveCommandResponseFailed(is); case GET_DIAGNOSTIC_COMMAND: // no-op this can be stored in db later if actually used for ops support // for now, look at the hex dump of the message in the log to // see the diagnostic values. return null; case INVALID_MSG_CONTENT: logger.log(Level.FINER, "Invalid content message 0x{0}", EncodeDecodeOtaMessage.toHexString(b)); return new InvalidContentMessage(); default: throw new IllegalArgumentException("Command not understood 0x" + EncodeDecodeOtaMessage.toHexString(b)); } } // // FAILED private static SetParameterFailedResponse decodeSetParameterFailed(InputStream is) throws IOException { is.read(); //length always 2 int parameterId = is.read(); int failedReason = is.read(); return new SetParameterFailedResponse(parameterId, decodeFailedReason(failedReason)); } private static GetParameterFailedResponse decodeGetParameterFailed(InputStream is) throws IOException { is.read(); //length always 2 int parameterId = is.read(); int failedReason = is.read(); return new GetParameterFailedResponse(parameterId, decodeFailedReason(failedReason)); } private static EraseCommandFailedResponse decodeEraseCommandFailed(InputStream is) throws IOException { is.read(); //length always 2 int parameterId = is.read(); int failedReason = is.read(); return new EraseCommandFailedResponse(parameterId, decodeFailedReason(failedReason)); } private static RetrieveCommandFailedResponse decodeRetrieveCommandResponseFailed(InputStream is) throws IOException { is.read(); //length always 2 int parameterId = is.read(); int failedReason = is.read(); return new RetrieveCommandFailedResponse(parameterId, decodeFailedReason(failedReason)); } private static String decodeFailedReason(int failedReason) { switch (failedReason) { case FAILED_INVALID_COMMAND: return "Invalid command."; case FAILED_INVALID_PARAMETER_ID: return "Invalid parameter id."; case FAILED_INVALID_PARAMETER_VALUE: return "Invalid parameter value."; case FAILED_INVALID_COMMAND_LENGTH: return "Invalid command length."; case FAILED_PARAMETER_NOT_CHANGEABLE: return "Paramter not changeable."; case FAILED_ERROR_NOT_SPECIFIED: return "Error not specified."; } return "Unknown failed reason."; } private static GlucoMonDatabase decodeRetrieveCommand(Date messageSubmitDate, int retrieveType, InputStream is) throws IOException { /* The structure of a reading sent trough the network has a 6 byte compressed format. Reserved (4 bits) Extra info(2 bits): CT, Controll Test (1 bit) 0 means a blood sample 1 means a controll test TC, Time Correct (1 bit) 0 means time stamp of measurement in NOK 1 means time stamp of measurement is OK Value of reading (10 bit) : value from 000 to 600 601 : HIGH, Value higher then 600 602 : REXC, Range exceeded, no valid reading 1020 : TCORR, Time correction Date and Time (4byte): Standard 4 byte time format int reading2bytes; reading2bytes = 0x0800; //CT true reading2bytes = 0xF7FF; //CT false reading2bytes = 0x0400; //TC true reading2bytes = 0xFBFF; //TC false reading2bytes = 0x03FF; //10 bit all on reading value reading2bytes = 80; int CT_MASK = 0x0800; int TC_MASK = 0x0400; int TC_VALUE_MASK = 0x03FC; //1020 in reading value for a tc (2 flags?) int READING_VALUE_MASK = 0x03FF; System.out.println("ct=" + ((reading2bytes & CT_MASK) > 0) ); System.out.println("tc=" + ((reading2bytes & TC_MASK) > 0) ); System.out.println("readingValue=" + (reading2bytes & READING_VALUE_MASK) ); System.out.println("10bit all on reading = 0x03FF"); */ int numberOfGlucometers = is.read(); logger.finer("numberOfGlucometers=" + numberOfGlucometers); if (numberOfGlucometers > 64) { throw new IllegalStateException("Too many glucose meters (" + numberOfGlucometers + "); invalid data"); } //(retrieveType == SEND_READING_NORMAL_RESPONSE) either alarm or normal send GlucoMonDatabase db = new GlucoMonDatabase((retrieveType == SEND_READING_ALARM_RESPONSE)); Calendar glucoMonTimeStamp = null; for (int i = 0; i < numberOfGlucometers; i++) { String serialNumber = decryptSerialNumber(is); logger.log(Level.FINE, "serialNumber={0}", serialNumber); MedicalDevice currentGlucoseMeter = new MedicalDevice(serialNumber); int numberOfReadings = (short) ((is.read() << 8) + (is.read() << 0)); // DIY readShort() to avoid creating DataInputStream just for this logger.finer("currentGlucoseMeter(#" + i + ")=" + currentGlucoseMeter + ",numberOfReadings=" + numberOfReadings); // There will be > 150 readings when getting module database (up to 4k readings...) // if ( numberOfReadings > 150 ) { // throw new IllegalStateException( "Too many readings (" + numberOfReadings + "); invalid data" ); // } for (int j = 0; j < numberOfReadings; j++) { int reading2bytes = ((is.read() << 8) + (is.read() << 0)); long reading4ByteTimeSeconds = ((is.read() << 24) + (is.read() << 16) + (is.read() << 8) + (is.read() << 0)); logger.finer("reading2bytes=" + IntegerHelper.toHexString(reading2bytes) + ",reading4ByteTimeSeconds=" + reading4ByteTimeSeconds + ",reading4ByteTimeSeconds=" + IntegerHelper.toHexString(reading4ByteTimeSeconds)); if ((reading2bytes & TC_MASK) == 0 && (reading2bytes & TC_VALUE_MASK) == TC_VALUE_MASK) { // save the offset for subsequent readings glucoMonTimeStamp = Calendar.getInstance(); glucoMonTimeStamp.setTimeInMillis(reading4ByteTimeSeconds * 1000); logger.finer( "readingOffset=" + j + ",glucoMonTimeStamp=" + glucoMonTimeStamp.getTime().toString()); } else { Calendar timeStamp = Calendar.getInstance(); timeStamp.setTimeInMillis(reading4ByteTimeSeconds * 1000); currentGlucoseMeter.addDataPoint( decodeReading(j, messageSubmitDate, reading2bytes, timeStamp, glucoMonTimeStamp)); } } db.addGlucoseMeter(currentGlucoseMeter); } return db; } // arg for time bytes private static DataPoint decodeReading(int readingOffset, Date messageSubmitDate, int reading2bytes, Calendar timeStamp, Calendar glucoMonTimeStamp) { logger.finer("readingOffset=" + readingOffset + ",reading2bytes=" + IntegerHelper.toHexString(reading2bytes) + ",timeStamp=" + timeStamp.getTime().toString()); if (glucoMonTimeStamp != null) { logger.finer("glucoMonTimeStamp=" + glucoMonTimeStamp.getTime().toString()); } DataPoint reading = new DataPoint(); reading.setValue(reading2bytes & READING_VALUE_MASK); if ((reading2bytes & TC_MASK) == 0 && glucoMonTimeStamp != null) { //only set if time is suspect //reading.setMedicalDeviceTimestamp( glucoMonTimeStamp.getTime() ); } reading.setTimestamp(timeStamp.getTime()); reading.setIsControl((reading2bytes & CT_MASK) > 0); reading.setOriginated(messageSubmitDate); return reading; } private static GlucoMonDatabase decodeRetrieveCommand19Bit(Date messageSubmitDate, int retrieveType, InputStream is) throws IOException { /* The structure of a reading sent trough the network has a 4 byte compressed format. Value of reading (10 bit) : value from 000 to 600 601 : HIGH, Value higher then 600 602 : REXC, Range exceeded, no valid reading 1020 : TCORR, Time correction Date and Time (19bit): resolution 1 minute Every 3 hours, the 59th minute will be rounded to the 58th or 00th minute (Compression). saved in minutes Extra info(3 bit): TC, Time Correct (1 bit) 0 means time stamp of measurement in NOK 1 means time stamp of measurement is OK CT, Controll Test (1 bit) 0 means a blood sample 1 means a controll test Reserved (1 bit) */ int numberOfGlucometers = is.read(); //(retrieveType == SEND_READING_NORMAL_RESPONSE) either alarm or normal send GlucoMonDatabase db = new GlucoMonDatabase((retrieveType == SEND_READING_ALARM_RESPONSE)); long timeCorrectionOffsetMinutes = 0; for (int i = 0; i < numberOfGlucometers; i++) { MedicalDevice currentGlucoseMeter = new MedicalDevice(decryptSerialNumber(is)); int numberOfReadings = (short) ((is.read() << 8) + (is.read() << 0)); // DIY readShort() to aoid creating DataInputStream just for this for (int j = 0; j < numberOfReadings; j++) { // DIY readInt() to aoid creating DataInputStream just for this int reading = ((is.read() << 24) + (is.read() << 16) + (is.read() << 8) + (is.read() << 0)); if (((reading >>> 22) & TIME_CORRECTION_READING_FLAG_VALUE) == TIME_CORRECTION_READING_FLAG_VALUE) { int offset = (reading & 0x3ffff8) >> 3; if ((0x40000 & offset) != 0) { logger.finer("OFFSET NEGATIVE (sign bit (19th) is flipped, convert to negative number)"); offset = (offset) - 0x80000; } timeCorrectionOffsetMinutes = convert19BitDateMinutes(offset); //add back minutes lost in compression logger.finer("OFFSET=" + offset + ",bin=" + Integer.toBinaryString(offset) + ",convert19BitDateMinutes=" + timeCorrectionOffsetMinutes); } else { DataPoint dp = decodeReading19Bit(messageSubmitDate, reading, timeCorrectionOffsetMinutes); currentGlucoseMeter.addDataPoint(dp); } } db.addGlucoseMeter(currentGlucoseMeter); } return db; } private static DataPoint decodeReading19Bit(Date messageSubmitDate, int readingBytes, long timeCorrectionOffsetMinutes) { logger.finer("readingBytes=" + IntegerHelper.toHexString(readingBytes) + ",timeCorrectionOffsetMinutes=" + timeCorrectionOffsetMinutes); DataPoint reading = new DataPoint(); reading.setValue(readingBytes >>> 22); if ((readingBytes & 0x04) == 0) { //only set if time is suspect reading.setTimeCorrectionOffset((int) timeCorrectionOffsetMinutes); } reading.setTimestamp(decode19BitDate((readingBytes & 0x3ffff8) >>> 3).getTime()); reading.setIsControl(((readingBytes & 0x02) > 0)); // // System.out.println( reading.getDateTime() ); // System.out.println( reading.getCorrectedDateTime() ); // System.out.println( reading.getTimeCorrectionOffset() ); // reading.setOriginated(messageSubmitDate); return reading; } private static SetParameterAck decodeSetParamterAck(InputStream is) throws IOException { //parameter length is always 0x01 is.read(); int parameterId = is.read(); switch (parameterId) { case PARAMETER_OTA_TIME_SETTINGS: return new SetParameterOtaTimeSettingsAck(); case PARAMETER_ENABLE_FLEXSUITE_HEADER: return new SetParameterEnableFlexSuiteHeaderAck(); case PARAMETER_MESSAGE_COUNTS: return new SetParameterMessageCountAck(); case PARAMETER_ENABLE_ENCRYPTION: return new SetParameterEnableEncryptionAck(); case PARAMETER_ENCRYPTION_KEY: return new SetParameterEncryptionKeyAck(); case PARAMETER_RESET_ENCRYPTION_KEY: return new SetParameterResetEncryptionKeyAck(); case PARAMETER_RADIO_SLEEP: return new SetParameterRadioSleepAck(); case PARAMETER_ALARM_LIMIT: return new SetParameterAlarmLimitAck(); case PARAMETER_AUTODELETE: return new SetParameterAutoDeleteAck(); case PARAMETER_BATTERY_STATUS: return new SetParameterBatteryStatusAck(); case PARAMETER_MAIL_TO_ADDRESS: return new SetParameterMailToAck(); case PARAMETER_AUTO_SEND_NETWORK_INFO: return new SetParameterAutoSendNetworkInfoAck(); case PARAMETER_SET_DEBUG_INFO: return new SetParameterDebugInfoAck(); case PARAMETER_BATTERY_OFFSET: return new SetParameterBatteryOffsetAck(); default: throw new IllegalArgumentException( "parameterId not understood 0x" + EncodeDecodeOtaMessage.toHexString(parameterId)); } } //Command 0x2A Get_Parameter command //Length length Get_Parameter length (1 byte ) //Parameter ID ID See Parameter ID list ( 1 byte ) //Value value Get_Parameter value //0x10 Time Setting 1 byte See description //0x20 Radio_Sleep 2 byte, as described 2 compressed time values, see Compressed 1 byte time. //0x30 Alarm_Limit 2 byte, as described Low limit (1 byte) , high limit ( 1 byte ) values 0 201 , default 0, 201 //0x40 Autodelete [GM] 1 byte TRUE (1) when enabled FALSE (0) is disabled //0x50 Battery status 1 byte Status value: 0 15 /** * @param parameterLength special parm for this because individual gets must read length, while get all do not use length in format, and must pass a calculated length for email. **/ private static GetParameterAck decodeGetParamterAck(InputStream is, int parameterLength) throws IOException { return decodeGetParamterAck(is, parameterLength, false); } private static GetParameterAck decodeGetParamterAck(InputStream is, int parameterLength, boolean is19BitDateFormat) throws IOException { if (parameterLength == 0) { parameterLength = is.read(); } int parameterId = is.read(); switch (parameterId) { case PARAMETER_OTA_TIME_SETTINGS: int timeSetting = is.read(); boolean updateGlucoseMeterWithNetworkTime = (timeSetting & 0x80) == 0x80; boolean timeCorrectionPositive = (timeSetting & 0x40) == 0x40; int profileTimeZoneUtcOffsetMinutes = timeSetting & 0x3F; profileTimeZoneUtcOffsetMinutes = timeCorrectionPositive ? profileTimeZoneUtcOffsetMinutes : profileTimeZoneUtcOffsetMinutes * -1; profileTimeZoneUtcOffsetMinutes *= 30; return new GetParameterOtaTimeSettingsAck(updateGlucoseMeterWithNetworkTime, profileTimeZoneUtcOffsetMinutes); case PARAMETER_ENABLE_ENCRYPTION: boolean enableEncryption = (is.read() == 1); return new GetParameterEnableEncryptionAck(enableEncryption); case PARAMETER_ENCRYPTION_KEY: byte[] secretKey = { (byte) is.read(), (byte) is.read(), (byte) is.read(), (byte) is.read(), (byte) is.read(), (byte) is.read(), (byte) is.read(), (byte) is.read(), (byte) is.read(), (byte) is.read(), (byte) is.read(), (byte) is.read(), (byte) is.read(), (byte) is.read(), (byte) is.read(), (byte) is.read() }; return new GetParameterEncryptionKeyAck(secretKey); case PARAMETER_ENABLE_FLEXSUITE_HEADER: boolean enableFlexSuiteHeader = (is.read() == 1); return new GetParameterEnableFlexSuiteHeaderAck(enableFlexSuiteHeader); case PARAMETER_MESSAGE_COUNTS: int stored = is.read(); int sent = is.read(); int failed = is.read(); return new GetParameterMessageCountAck(stored, sent, failed); case PARAMETER_RADIO_SLEEP: int startTimeMinutes = is.read() * 6; int endTimeMinutes = is.read() * 6; return new GetParameterRadioSleepAck(startTimeMinutes, endTimeMinutes); case PARAMETER_ALARM_LIMIT: int lowLimit = is.read() * 3; int highLimit = is.read() * 3; return new GetParameterAlarmLimitAck(lowLimit, highLimit); case PARAMETER_AUTODELETE: boolean autoDeleteGlucoseMeter = (is.read() == 1); return new GetParameterAutoDeleteAck(autoDeleteGlucoseMeter); case PARAMETER_BATTERY_STATUS: int batteryStatus = is.read(); return new GetParameterBatteryStatusAck(batteryStatus); case PARAMETER_MAIL_TO_ADDRESS: byte[] emailAddress = new byte[parameterLength - 1]; is.read(emailAddress); return new GetParameterMailToAck(new String(emailAddress)); case PARAMETER_AUTO_SEND_NETWORK_INFO: boolean autoSendNetworkInfo = (is.read() == 1); return new GetParameterAutoSendNetworkInfoAck(autoSendNetworkInfo); case PARAMETER_NETWORK_INFO: int serviceProviderId = ((is.read() << 8) + (is.read() << 0)); int zoneId = ((is.read() << 8) + (is.read() << 0)); int subZoneId = is.read(); int colorCode = is.read(); return new GetParameterNetworkInfoAck(serviceProviderId, zoneId, subZoneId, colorCode); case PARAMETER_SOFTWARE_VERSION: int softwareVersion = is.read(); return new GetParameterSoftwareVersionAck(softwareVersion); case PARAMETER_LED_STATE: int ledState = is.read(); return new GetParameterLedStateAck(ledState); case PARAMETER_SET_DEBUG_INFO: boolean isDebugOn = (is.read() == 1); return new GetParameterDebugInfoAck(isDebugOn); case PARAMETER_BATTERY_VOLTAGE_MV: int batteryVoltage = ((is.read() << 8) + (is.read() << 0)); return new GetParameterBatteryVoltageAck(batteryVoltage); case PARAMETER_BATTERY_LOW: int batteryVoltageLow = ((is.read() << 8) + (is.read() << 0)); return new GetParameterBatteryLowAck(batteryVoltageLow); case PARAMETER_BATTERY_OFFSET: int batteryVoltageOffset = ((is.read() << 8) + (is.read() << 0)); return new GetParameterBatteryOffsetAck(batteryVoltageOffset); case PARAMETER_BATTERY_MAX: int batteryVoltageMax = ((is.read() << 8) + (is.read() << 0)); return new GetParameterBatteryMaxAck(batteryVoltageMax); case PARAMETER_DUTY_CYCLES: int trickleCycle = is.read(); int fastCycle = is.read(); return new GetParameterDutyCyclesAck(trickleCycle, fastCycle); case PARAMETER_LAST_CHARGE_TIME: if (is19BitDateFormat) { int lastChargeTime = ((is.read() << 16) + (is.read() << 8) + (is.read() << 0)); return new GetParameterLastChargeTimeAck(decode19BitDate(lastChargeTime & 0x7FFFF)); } else { // 4 byte c time long lastChargeTimeSeconds = ((is.read() << 24) + (is.read() << 16) + (is.read() << 8) + (is.read() << 0)); Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(lastChargeTimeSeconds * 1000); return new GetParameterLastChargeTimeAck(cal); } case PARAMETER_GET_ALL_PARAMETERS: return decodeRetrieveAllParameters(is, parameterLength, is19BitDateFormat); default: throw new IllegalArgumentException( "parameterId not understood 0x" + EncodeDecodeOtaMessage.toHexString(parameterId)); } } private static GetParameterAllAck decodeRetrieveAllParameters(InputStream is, int totalLength, boolean decodeReading19Bit) throws IOException { GetParameterOtaTimeSettingsAck ota = (GetParameterOtaTimeSettingsAck) decodeGetParamterAck(is, -1); GetParameterRadioSleepAck radioSleep = (GetParameterRadioSleepAck) decodeGetParamterAck(is, -1); GetParameterAlarmLimitAck alarmLimit = (GetParameterAlarmLimitAck) decodeGetParamterAck(is, -1); GetParameterAutoDeleteAck autoDelete = (GetParameterAutoDeleteAck) decodeGetParamterAck(is, -1); GetParameterBatteryStatusAck batteryStatus = (GetParameterBatteryStatusAck) decodeGetParamterAck(is, -1); GetParameterSoftwareVersionAck softwareVersion = (GetParameterSoftwareVersionAck) decodeGetParamterAck(is, -1); GetParameterLedStateAck ledState = (GetParameterLedStateAck) decodeGetParamterAck(is, -1); GetParameterAutoSendNetworkInfoAck autoSend = (GetParameterAutoSendNetworkInfoAck) decodeGetParamterAck(is, -1); GetParameterNetworkInfoAck infoAck = (GetParameterNetworkInfoAck) decodeGetParamterAck(is, -1); GetParameterEnableFlexSuiteHeaderAck enableFlexSuiteHeader = (GetParameterEnableFlexSuiteHeaderAck) decodeGetParamterAck( is, -1); GetParameterMessageCountAck messageCountAck = (GetParameterMessageCountAck) decodeGetParamterAck(is, -1); GetParameterEnableEncryptionAck enableEncryption = (GetParameterEnableEncryptionAck) decodeGetParamterAck( is, -1); GetParameterDebugInfoAck debugInfo = (GetParameterDebugInfoAck) decodeGetParamterAck(is, -1); GetParameterBatteryVoltageAck batteryVoltage = (GetParameterBatteryVoltageAck) decodeGetParamterAck(is, -1); GetParameterDutyCyclesAck dutyCycles = (GetParameterDutyCyclesAck) decodeGetParamterAck(is, -1); GetParameterLastChargeTimeAck chargeTime = (GetParameterLastChargeTimeAck) decodeGetParamterAck(is, -1, decodeReading19Bit); GetParameterBatteryLowAck batteryLowVoltage = (GetParameterBatteryLowAck) decodeGetParamterAck(is, -1); GetParameterBatteryOffsetAck batteryOffsetVoltage = (GetParameterBatteryOffsetAck) decodeGetParamterAck(is, -1); GetParameterBatteryMaxAck batteryMaxVoltage = (GetParameterBatteryMaxAck) decodeGetParamterAck(is, -1); GetParameterMailToAck mailTo = (GetParameterMailToAck) decodeGetParamterAck(is, totalLength - 7); return new GetParameterAllAck(ota, radioSleep, alarmLimit, autoDelete, batteryStatus, softwareVersion, ledState, autoSend, infoAck, enableFlexSuiteHeader, messageCountAck, enableEncryption, debugInfo, batteryVoltage, dutyCycles, chargeTime, batteryLowVoltage, batteryOffsetVoltage, batteryMaxVoltage, mailTo); } private static EraseAck decodeEraseCommandAck(InputStream is) throws IOException { int length = is.read(); int typeCode = is.read(); String type = null; switch (typeCode) { case ERASE_ALL_READINGS: type = "ALL_READINGS"; break; case ERASE_ALL_READINGS_FOR_SN: type = "ALL_READINGS_FOR_SN"; break; case ERASE_ALL_DELIVERED_READINGS: type = "ALL_DELIVERED"; break; case ERASE_ALL_DELIVERED_READINGS_FOR_SN: type = "ALL_DELIVERED_FOR_SN"; break; case ERASE_ALL_SN_ALL_ENTRIES: type = "ALL_SN_AND_ALL_READINGS"; break; case ERASE_ALL_ENTRIES_FOR_SN: type = "SN_AND_READINGS"; break; default: type = "UNKNOWN"; } if (1 == length) { return new EraseAck(type); } else { return new EraseAck(type, decryptSerialNumber(is)); } } // TEST MAIN public static void main(String[] args) throws Exception { // int reading; // reading = 0xFF001B30; // reading = 0xFF3D85D0; // reading = 0xfff54b18; // logger.fine( "reading >>> 22" + (reading >>> 22) + "," + ((reading >>> 22) & 1020) ); // if ( ((reading >>> 22) & TIME_CORRECTION_READING_FLAG_VALUE) == TIME_CORRECTION_READING_FLAG_VALUE ) { // int offset = (reading & 0x3ffff8) >> 3; // if ( (0x40000 & offset ) != 0 ) { // logger.fine( "OFFSET NEGATIVE (sign bit (19th) is flipped, convert to negative number)" ); // offset = (offset) - 0x80000; // } // // int timeCorrectionOffsetMinutes = convert19BitDateMinutes( offset ); //add back minutes lost in compression // logger.fine( "OFFSET=" + offset + ",bin=" + Integer.toBinaryString( offset ) + ",convert19BitDateMinutes=" + timeCorrectionOffsetMinutes ); // //currentGlucoseMeter.addReading( decodeReading( reading, timeCorrectionOffsetMinutes ) ); //DEBUG REMOVE@!!!!! // } // // // Reading : 0x32 0x0F 0x9E 0x34 // // Date : 03/30/04 08:16:00 // // Value : 200 , TC = 1 CT = 0 // logger.fine( decodeReading( 0x320F9E34, 0xff ).toString() ); // // // // Reading : 0x35 0xDF 0xA1 0xB4 // // Date : 03/30/04 10:09:00 // // Value : 215, TC = 1 CT = 0 // logger.fine( decodeReading( 0x35DFA1B4, 0xff ).toString() ); // // // // Reading : 0x96 0x5F 0xA7 0x24 // // Date : 03/30/04 13:04:30 // // Value : HIGH, TC = 1 CT = 0 // logger.fine( decodeReading( 0x965FA724, 0xff).toString() ); // // // // Reading : 0xFF 0x00 0x1B 0x30 // // TimeOffset : - 14h 34min // // Value : TCORR, TC = 0 CT = 0 // // this is the time offset amount, indicating the time is changed and followoing readings time correct = 0 (NOK) based on this offset // logger.fine( decodeReading( 0xFF001B30, 0xff ).toString() ); // // logger.fine( decodeReading( 0x965FA720, 0xff ).toString() ); // logger.fine( decodeReading( 0x92AFFF27, 0xff ).toString() ); // // // //This is the difference between OTA time and [GM] time with a resolution of 1 minute. // // It is a signed value going from 150 days to +150 days. // // It is only reported if the time difference between the [GM] and the [DMD] is larger then 10 minutes and if OTA_Time settings are used. // // This is saved as a reading if a time offset has been detected. // // // //Example: // // Time on the [GM] changed, timeoffset = -15days, 4hour, 7 minutes // // // //1. Equal to 367 hours 7 minutes, equals to 21847 minutes. // //2. every 3 hours, we loose a minute, meaning we loose 122 minutes // //Converted into minutes: - 20294 // // Shifted into a reading: 0x00 0x03D 0x85 0xD0 // encryptSerialNumber( "QKP097ECT" ); // encryptSerialNumber( "QRG412BCT" ); // encryptSerialNumber( "RGZ48D6FT" ); // encodeGetReadings( false, "RGZ48D6FT" ); // //3D6E4BA236814C85 // //3D 6E 4B A2 36 81 4C 85 // byte[] b = new byte[8]; // b[0] = (byte)0x3d; // b[1] = (byte)0x6e; // b[2] = (byte)0x4b; // b[3] = (byte)0xa2; // b[4] = (byte)0x36; // b[5] = (byte)0x81; // b[6] = (byte)0x4c; // b[7] = (byte)0x85; // java.io.ByteArrayInputStream in = new java.io.ByteArrayInputStream( b ); // // //time offset after reading? // byte[] msg = new byte[] { (byte)0x5A, (byte) 0x01, (byte) 0x75, (byte) 0x4F, (byte) 0x08, (byte) 0xA4, (byte) 0x36, (byte) 0x95, (byte) 0x52, (byte) 0xF5, (byte) 0x00, (byte) 0x02, (byte) 0x22, (byte) 0xC0, (byte) 0x39, (byte) 0x50, (byte) 0xFF, (byte) 0x32, (byte) 0x23, (byte) 0xE0 }; // java.io.ByteArrayInputStream in = new java.io.ByteArrayInputStream( msg ); // logger.fine( decode( in ).toString() ); // DIY DATE FILE // logger.setLevel( java.util.logging.Level.OFF ); // java.io.FileOutputStream out = new java.io.FileOutputStream( "x:\\ericdate.txt" ); // for ( int i = 0; i < 524287; i++ ) { // out.write( (decode19BitDate( i ).toString() + "," + i + "\r\n").getBytes() ); // } // out.close(); int[] msg = encodeParameterSetMailToAddress("wctp.diabetech.net/wctp/"); byte[] byteMsg = new byte[msg.length]; for (int i = 0; i < msg.length; i++) { byteMsg[i] = (byte) msg[i]; } System.out.println(new String(Base64.encodeBase64(byteMsg))); msg = encodeParameterSetAutoDeleteGlucoseMeter(true); byteMsg = new byte[msg.length]; for (int i = 0; i < msg.length; i++) { byteMsg[i] = (byte) msg[i]; } System.out.println(new String(Base64.encodeBase64(byteMsg))); msg = encodeParameterSetAutoDeleteGlucoseMeter(false); byteMsg = new byte[msg.length]; for (int i = 0; i < msg.length; i++) { byteMsg[i] = (byte) msg[i]; } System.out.println(new String(Base64.encodeBase64(byteMsg))); msg = encodeEventReadGlucoseMeter(); byteMsg = new byte[msg.length]; for (int i = 0; i < msg.length; i++) { byteMsg[i] = (byte) msg[i]; } System.out.println("encodeEventReadGlucoseMeter:" + new String(Base64.encodeBase64(byteMsg))); msg = encodeParameterSetEnableEncryption(false); byteMsg = new byte[msg.length]; for (int i = 0; i < msg.length; i++) { byteMsg[i] = (byte) msg[i]; } System.out.println("encodeParameterSetEnableEncryption(false):" + new String(Base64.encodeBase64(byteMsg))); msg = encodeParameterSetEnableEncryption(true); byteMsg = new byte[msg.length]; for (int i = 0; i < msg.length; i++) { byteMsg[i] = (byte) msg[i]; } System.out.println("encodeParameterSetEnableEncryption(true):" + new String(Base64.encodeBase64(byteMsg))); } }