sphero-pwn
Version:
Driver for Sphero robots
175 lines (157 loc) • 5.66 kB
text/coffeescript
# The tokenizer is a finite state machine that consumes characters and
# transitions between the states below.
START_OF_PACKET = 0 # Looking for the start-of-packet byte, 0xFF
PACKET_TYPE = 1 # Reading the packet type
RESPONSE_CODE = 2 # Reading the response code byte
RESPONSE_SEQUENCE = 3 # Reading the response sequence byte
RESPONSE_DATA_LENGTH = 4 # Reading the response data length
RESPONSE_DATA = 5 # Reading response data bytes
RESPONSE_CHECKSUM = 6 # Reading the response checksum
ASYNC_ID_CODE = 7 # Reading the async message ID code (type)
ASYNC_DATA_LENGTH_MSB = 8 # Reading the MSB of the async message's data length
ASYNC_DATA_LENGTH_LSB = 9 # Reading the LSB of the async message's data length
ASYNC_DATA = 10 # Reading async message data bytes
ASYNC_CHECKSUM = 11 # Reading the async message's checksum
# Splits incoming data bytes into packets.
#
# This does not implement the EventEmitter interface because the tokenizer is
# expected to have a single object (a Session instance) listening to all its
# events.
module.exports = class Tokenizer
# Creates a tokenizer with no stored data.
constructor: ->
# The response code or async message ID code.
# Called when a data stream error is encountered.
#
# @param {Error} error the error that was encountered, with a descriptive
# message
onError: (error) ->
return
# Called when a byte is encountered outside a binary packet.
#
# This should only happen when the robot's shell is used. Returned characters
# should be displayed to the user as they are.
#
# @param {String} char a one-character string
onText: (char) ->
return
# Called when a command response is decoded from the stream.
#
# @param {Response} response a fully decoded response
onResponse: (response) ->
return
# Called when an asynchronous message is decoded from the stream.
#
# @param {Async} a fully decoded asynchronous message
onAsync: (async) ->
return
# Tokenizes a new chunk of data.
#
# @param {Buffer} data the chunk of data to be tokenized
consume: (data) ->
i = 0
while i < data.length
byte = data[i]
i += 1
switch @_state
# Packet header.
when START_OF_PACKET
if byte is 0xFF
else
when PACKET_TYPE
if byte is 0xFF
else if byte is 0xFE
else
# Response header.
when RESPONSE_CODE
when RESPONSE_SEQUENCE
when RESPONSE_DATA_LENGTH
# Async message header.
when ASYNC_ID_CODE
when ASYNC_DATA_LENGTH_MSB
when ASYNC_DATA_LENGTH_LSB
# Data.
when RESPONSE_DATA, ASYNC_DATA
# TODO(pwnall): This can be optimized by grabbing all the data left
# in the buffer at once.
# Response checksum and construction.
when RESPONSE_CHECKSUM
expected = @_checksum ^ 0xFF
if expected != byte
else
response =
code: @_code, sequence: @_sequence, data: @_data
# Async message checksum and construction.
when ASYNC_CHECKSUM
expected = @_checksum ^ 0xFF
if expected != byte
else
async =
idCode: @_code, data: @_data
# Create a new data buffer for a response or async message.
#
# @param {Number} dataSize the number of data bytes expected
_newDataBuffer: (dataSize) ->
return
# Transition the FSM to the next state if we're done reading all the data.
_checkDoneReadingData: ->
return unless @_dataLeft is 0
if @_state == RESPONSE_DATA
else
return