001// Copyright (c) FIRST and other WPILib contributors. 002// Open Source Software; you can modify and/or share it under the terms of 003// the WPILib BSD license file in the root directory of this project. 004 005package org.wpilib.datalog; 006 007import java.nio.BufferUnderflowException; 008import java.nio.ByteBuffer; 009import java.nio.ByteOrder; 010import java.nio.DoubleBuffer; 011import java.nio.FloatBuffer; 012import java.nio.LongBuffer; 013import java.nio.charset.StandardCharsets; 014import java.util.InputMismatchException; 015 016/** 017 * A record in the data log. May represent either a control record (entry == 0) or a data record. 018 * Used only for reading (e.g. with DataLogReader). 019 */ 020public class DataLogRecord { 021 private static final int kControlStart = 0; 022 private static final int kControlFinish = 1; 023 private static final int kControlSetMetadata = 2; 024 025 DataLogRecord(int entry, long timestamp, ByteBuffer data) { 026 m_entry = entry; 027 m_timestamp = timestamp; 028 m_data = data; 029 m_data.order(ByteOrder.LITTLE_ENDIAN); 030 } 031 032 /** 033 * Gets the entry ID. 034 * 035 * @return entry ID 036 */ 037 public int getEntry() { 038 return m_entry; 039 } 040 041 /** 042 * Gets the record timestamp. 043 * 044 * @return Timestamp, in integer microseconds 045 */ 046 public long getTimestamp() { 047 return m_timestamp; 048 } 049 050 /** 051 * Gets the size of the raw data. 052 * 053 * @return size 054 */ 055 public int getSize() { 056 return m_data.remaining(); 057 } 058 059 /** 060 * Gets the raw data. Use the GetX functions to decode based on the data type in the entry's start 061 * record. 062 * 063 * @return byte array 064 */ 065 public byte[] getRaw() { 066 ByteBuffer buf = getRawBuffer(); 067 byte[] arr = new byte[buf.remaining()]; 068 buf.get(arr); 069 return arr; 070 } 071 072 /** 073 * Gets the raw data. Use the GetX functions to decode based on the data type in the entry's start 074 * record. 075 * 076 * @return byte buffer 077 */ 078 public ByteBuffer getRawBuffer() { 079 ByteBuffer buf = m_data.duplicate(); 080 buf.order(ByteOrder.LITTLE_ENDIAN); 081 return buf; 082 } 083 084 /** 085 * Returns true if the record is a control record. 086 * 087 * @return True if control record, false if normal data record. 088 */ 089 public boolean isControl() { 090 return m_entry == 0; 091 } 092 093 /** 094 * Returns true if the record is a start control record. Use GetStartData() to decode the 095 * contents. 096 * 097 * @return True if start control record, false otherwise. 098 */ 099 public boolean isStart() { 100 return m_entry == 0 && m_data.remaining() >= 17 && m_data.get(0) == kControlStart; 101 } 102 103 /** 104 * Returns true if the record is a finish control record. Use GetFinishEntry() to decode the 105 * contents. 106 * 107 * @return True if finish control record, false otherwise. 108 */ 109 public boolean isFinish() { 110 return m_entry == 0 && m_data.remaining() == 5 && m_data.get(0) == kControlFinish; 111 } 112 113 /** 114 * Returns true if the record is a set metadata control record. Use GetSetMetadataData() to decode 115 * the contents. 116 * 117 * @return True if set metadata control record, false otherwise. 118 */ 119 public boolean isSetMetadata() { 120 return m_entry == 0 && m_data.remaining() >= 9 && m_data.get(0) == kControlSetMetadata; 121 } 122 123 /** 124 * Data contained in a start control record as created by DataLog.start() when writing the log. 125 * This can be read by calling getStartData(). 126 */ 127 public static class StartRecordData { 128 StartRecordData(int entry, String name, String type, String metadata) { 129 this.entry = entry; 130 this.name = name; 131 this.type = type; 132 this.metadata = metadata; 133 } 134 135 /** Entry ID; this will be used for this entry in future records. */ 136 public final int entry; 137 138 /** Entry name. */ 139 public final String name; 140 141 /** Type of the stored data for this entry, as a string, e.g. "double". */ 142 public final String type; 143 144 /** Initial metadata. */ 145 public final String metadata; 146 } 147 148 /** 149 * Decodes a start control record. 150 * 151 * @return start record decoded data 152 * @throws InputMismatchException on error 153 */ 154 public StartRecordData getStartData() { 155 if (!isStart()) { 156 throw new InputMismatchException("not a start record"); 157 } 158 ByteBuffer buf = getRawBuffer(); 159 buf.position(1); // skip over control type 160 int entry = buf.getInt(); 161 String name = readInnerString(buf); 162 String type = readInnerString(buf); 163 String metadata = readInnerString(buf); 164 return new StartRecordData(entry, name, type, metadata); 165 } 166 167 /** 168 * Data contained in a set metadata control record as created by DataLog.setMetadata(). This can 169 * be read by calling getSetMetadataData(). 170 */ 171 public static class MetadataRecordData { 172 MetadataRecordData(int entry, String metadata) { 173 this.entry = entry; 174 this.metadata = metadata; 175 } 176 177 /** Entry ID. */ 178 public final int entry; 179 180 /** New metadata for the entry. */ 181 public final String metadata; 182 } 183 184 /** 185 * Decodes a finish control record. 186 * 187 * @return finish record entry ID 188 * @throws InputMismatchException on error 189 */ 190 public int getFinishEntry() { 191 if (!isFinish()) { 192 throw new InputMismatchException("not a finish record"); 193 } 194 return m_data.getInt(1); 195 } 196 197 /** 198 * Decodes a set metadata control record. 199 * 200 * @return set metadata record decoded data 201 * @throws InputMismatchException on error 202 */ 203 public MetadataRecordData getSetMetadataData() { 204 if (!isSetMetadata()) { 205 throw new InputMismatchException("not a set metadata record"); 206 } 207 ByteBuffer buf = getRawBuffer(); 208 buf.position(1); // skip over control type 209 int entry = buf.getInt(); 210 String metadata = readInnerString(buf); 211 return new MetadataRecordData(entry, metadata); 212 } 213 214 /** 215 * Decodes a data record as a boolean. Note if the data type (as indicated in the corresponding 216 * start control record for this entry) is not "boolean", invalid results may be returned. 217 * 218 * @return boolean value 219 * @throws InputMismatchException on error 220 */ 221 public boolean getBoolean() { 222 try { 223 return m_data.get(0) != 0; 224 } catch (IndexOutOfBoundsException ex) { 225 throw new InputMismatchException(); 226 } 227 } 228 229 /** 230 * Decodes a data record as an integer. Note if the data type (as indicated in the corresponding 231 * start control record for this entry) is not "int64", invalid results may be returned. 232 * 233 * @return integer value 234 * @throws InputMismatchException on error 235 */ 236 public long getInteger() { 237 try { 238 return m_data.getLong(0); 239 } catch (BufferUnderflowException | IndexOutOfBoundsException ex) { 240 throw new InputMismatchException(); 241 } 242 } 243 244 /** 245 * Decodes a data record as a float. Note if the data type (as indicated in the corresponding 246 * start control record for this entry) is not "float", invalid results may be returned. 247 * 248 * @return float value 249 * @throws InputMismatchException on error 250 */ 251 public float getFloat() { 252 try { 253 return m_data.getFloat(0); 254 } catch (BufferUnderflowException | IndexOutOfBoundsException ex) { 255 throw new InputMismatchException(); 256 } 257 } 258 259 /** 260 * Decodes a data record as a double. Note if the data type (as indicated in the corresponding 261 * start control record for this entry) is not "double", invalid results may be returned. 262 * 263 * @return double value 264 * @throws InputMismatchException on error 265 */ 266 public double getDouble() { 267 try { 268 return m_data.getDouble(0); 269 } catch (BufferUnderflowException | IndexOutOfBoundsException ex) { 270 throw new InputMismatchException(); 271 } 272 } 273 274 /** 275 * Decodes a data record as a string. Note if the data type (as indicated in the corresponding 276 * start control record for this entry) is not "string", invalid results may be returned. 277 * 278 * @return string value 279 */ 280 public String getString() { 281 return new String(getRaw(), StandardCharsets.UTF_8); 282 } 283 284 /** 285 * Decodes a data record as a boolean array. Note if the data type (as indicated in the 286 * corresponding start control record for this entry) is not "boolean[]", invalid results may be 287 * returned. 288 * 289 * @return boolean array 290 */ 291 public boolean[] getBooleanArray() { 292 boolean[] arr = new boolean[m_data.remaining()]; 293 for (int i = 0; i < m_data.remaining(); i++) { 294 arr[i] = m_data.get(i) != 0; 295 } 296 return arr; 297 } 298 299 /** 300 * Decodes a data record as an integer array. Note if the data type (as indicated in the 301 * corresponding start control record for this entry) is not "int64[]", invalid results may be 302 * returned. 303 * 304 * @return integer array 305 * @throws InputMismatchException on error 306 */ 307 public long[] getIntegerArray() { 308 LongBuffer buf = getIntegerBuffer(); 309 long[] arr = new long[buf.remaining()]; 310 buf.get(arr); 311 return arr; 312 } 313 314 /** 315 * Decodes a data record as an integer array. Note if the data type (as indicated in the 316 * corresponding start control record for this entry) is not "int64[]", invalid results may be 317 * returned. 318 * 319 * @return integer buffer 320 * @throws InputMismatchException on error 321 */ 322 public LongBuffer getIntegerBuffer() { 323 if ((m_data.limit() % 8) != 0) { 324 throw new InputMismatchException("data size is not a multiple of 8"); 325 } 326 return m_data.asLongBuffer(); 327 } 328 329 /** 330 * Decodes a data record as a float array. Note if the data type (as indicated in the 331 * corresponding start control record for this entry) is not "float[]", invalid results may be 332 * returned. 333 * 334 * @return float array 335 * @throws InputMismatchException on error 336 */ 337 public float[] getFloatArray() { 338 FloatBuffer buf = getFloatBuffer(); 339 float[] arr = new float[buf.remaining()]; 340 buf.get(arr); 341 return arr; 342 } 343 344 /** 345 * Decodes a data record as a float array. Note if the data type (as indicated in the 346 * corresponding start control record for this entry) is not "float[]", invalid results may be 347 * returned. 348 * 349 * @return float buffer 350 * @throws InputMismatchException on error 351 */ 352 public FloatBuffer getFloatBuffer() { 353 if ((m_data.limit() % 4) != 0) { 354 throw new InputMismatchException("data size is not a multiple of 4"); 355 } 356 return m_data.asFloatBuffer(); 357 } 358 359 /** 360 * Decodes a data record as a double array. Note if the data type (as indicated in the 361 * corresponding start control record for this entry) is not "double[]", invalid results may be 362 * returned. 363 * 364 * @return double array 365 * @throws InputMismatchException on error 366 */ 367 public double[] getDoubleArray() { 368 DoubleBuffer buf = getDoubleBuffer(); 369 double[] arr = new double[buf.remaining()]; 370 buf.get(arr); 371 return arr; 372 } 373 374 /** 375 * Decodes a data record as a double array. Note if the data type (as indicated in the 376 * corresponding start control record for this entry) is not "double[]", invalid results may be 377 * returned. 378 * 379 * @return double buffer 380 * @throws InputMismatchException on error 381 */ 382 public DoubleBuffer getDoubleBuffer() { 383 if ((m_data.limit() % 8) != 0) { 384 throw new InputMismatchException("data size is not a multiple of 8"); 385 } 386 return m_data.asDoubleBuffer(); 387 } 388 389 /** 390 * Decodes a data record as a string array. Note if the data type (as indicated in the 391 * corresponding start control record for this entry) is not "string[]", invalid results may be 392 * returned. 393 * 394 * @return string array 395 * @throws InputMismatchException on error 396 */ 397 public String[] getStringArray() { 398 ByteBuffer buf = getRawBuffer(); 399 try { 400 int size = buf.getInt(); 401 // sanity check size 402 if (size > (buf.remaining() / 4)) { 403 throw new InputMismatchException("invalid size"); 404 } 405 String[] arr = new String[size]; 406 for (int i = 0; i < size; i++) { 407 arr[i] = readInnerString(buf); 408 } 409 return arr; 410 } catch (BufferUnderflowException | IndexOutOfBoundsException ex) { 411 throw new InputMismatchException(); 412 } 413 } 414 415 private String readInnerString(ByteBuffer buf) { 416 int size = buf.getInt(); 417 if (size > buf.remaining()) { 418 throw new InputMismatchException("invalid string size"); 419 } 420 byte[] arr = new byte[size]; 421 buf.get(arr); 422 return new String(arr, StandardCharsets.UTF_8); 423 } 424 425 private final int m_entry; 426 private final long m_timestamp; 427 private final ByteBuffer m_data; 428}