Home Reference Source Repository

src/parser.js

  1. import {crc32} from 'crc'
  2. import {set as objPathSet} from 'object-path'
  3.  
  4. /**
  5. * Parser creation helper class. Any subclasses should reimplement the parse method,
  6. * calling super.parse in the beginning. You have useful methods for reading regular C
  7. * datatypes from the buffer: readInt, readFloat, readChar... To use it in node-serialport
  8. * or the serial wrapper just pass in ::Parser.parse
  9. * Keep in mind not initialize this class as is. RE-IMPLEMENT Parser.parse!
  10. */
  11. export default class Parser {
  12. /**
  13. * @param {String} [endianess='LE'] Set byte-order to big endian (BE) or little endian (LE).
  14. */
  15. constructor(endianess = 'LE') {
  16. /**
  17. * Current packet being parsed.
  18. * @type {!Object}
  19. * @protected
  20. */
  21. this._raw = undefined
  22.  
  23. /**
  24. * Current position in the packet being parsed.
  25. * @type {Number}
  26. * @protected
  27. */
  28. this._i = 0
  29.  
  30. /**
  31. * Parsed output container.
  32. * @type {Object}
  33. * @property {String} crc.sent The CRC32 checkum sent along with the packet.
  34. * @property {String} crc.local The CRC32 checksum calculated from the sent data.
  35. */
  36. this.packet = Object.create(null)
  37.  
  38. /**
  39. * Raw packet byte endianess (Little Endian (LE) for the Arduino).
  40. * Acceptable values: LE, BE.
  41. * @type {String}
  42. */
  43. this.endianess = endianess
  44. }
  45.  
  46. /**
  47. * Parses a packet. Default implementation resets state and calculates and extracts the CRC32
  48. * checksum before parsing the new packet.
  49. * That's why, if you want any parsing at all to be performed, YOU MUST RE-IMPLEMENT THIS METHOD.
  50. * Preferrably calling this implementation before any parsing(but after any decoding
  51. * *wink* *wink* quasi-binary decoder).
  52. * @param {Buffer} rawPacket The packet to be parsed.
  53. * @return {Object} The parsed object.
  54. */
  55. parse(rawPacket) {
  56. // Change the current packet
  57. this._raw = rawPacket
  58.  
  59. // Reset state thingies
  60. this._i = 0
  61. this.packet = Object.create(null)
  62.  
  63. // Calculate CRC
  64. if (this._raw.length > 4) {
  65. this.setValue('crc.sent', rawPacket[`readUInt${this.endianess}`](rawPacket.length - 4, 4).toString(16))
  66. this.setValue('crc.local', crc32(rawPacket.slice(0, rawPacket.length - 4)).toString(16))
  67. }
  68.  
  69. // More parsing reserved to subclass
  70. return this.packet // Just to behave as the Docs say it will
  71. }
  72.  
  73. /**
  74. * The value setter called by read* helper functions.
  75. * Uses object path dot notation in the key and can convert values in one line.
  76. * @param {String} key The object path where the value will be inserted.
  77. * @param {mixed} val Anything you want to put.
  78. * @param {Function} [converter=identity] A function that converts the value.
  79. * @return {mixed} The provided value (val).
  80. */
  81. setValue(key, val, converter = v => v) {
  82. // Set the value in this.packet.[key]
  83. objPathSet(this.packet, key, converter(val))
  84. return val
  85. }
  86.  
  87. /**
  88. * Reads a signed integer of 1-6 bytes.
  89. * @param {String} key The dot notation path where the int goes to.
  90. * @param {Number} [size=1] The size in bytes of the integer (1 to 6 bytes only).
  91. * @param {Function} [converter=identity] A function that converts the value.
  92. * @return {Number} The read integer.
  93. */
  94. readInt(key, size = 1, converter) {
  95. // Get the value and update the index
  96. const val = this._raw[`readInt${this.endianess}`](this._i, size)
  97. this._i += size
  98.  
  99. return this.setValue(key, val, converter)
  100. }
  101.  
  102. /**
  103. * Reads an unsigned integer of 1-6 bytes.
  104. * @param {String} key The dot notation path where the int goes to.
  105. * @param {Number} [size=1] The size in bytes of the integer (1 to 6 bytes only).
  106. * @param {Function} [converter=identity] A function that converts the value.
  107. * @return {Number} The read integer.
  108. */
  109. readUInt(key, size = 1, converter) {
  110. // Get the value and update the index
  111. const val = this._raw[`readUInt${this.endianess}`](this._i, size)
  112. this._i += size
  113.  
  114. return this.setValue(key, val, converter)
  115. }
  116.  
  117. /**
  118. * Reads a 32-bit float.
  119. * @param {String} key The dot notation path where the float goes to.
  120. * @param {Function} [converter=identity] A function that converts the value.
  121. * @return {Number} The read float.
  122. */
  123. readFloat(key, converter) {
  124. // Get the value and update the index
  125. const val = this._raw[`readFloat${this.endianess}`](this._i)
  126. this._i += 4
  127.  
  128. return this.setValue(key, val, converter)
  129. }
  130.  
  131. /**
  132. * Reads a char.
  133. * @param {String} key The dot notation path where the char goes to.
  134. * @param {Function} [converter=identity] A function that converts the value.
  135. * @return {String} The read char.
  136. */
  137. readChar(key, converter) {
  138. return this.setValue(key, String.fromCharCode(this._raw[this._i++]), converter)
  139. }
  140.  
  141. /**
  142. * Reads a boolean.
  143. * @param {String} key The dot notation path where the boolean goes to.
  144. * @param {Function} [converter=identity] A function that converts the value.
  145. * @return {Boolean} The read boolean.
  146. */
  147. readBoolean(key, converter) {
  148. return this.setValue(key, Boolean(this._raw[this._i++]), converter)
  149. }
  150. }