Commit 99c85f7d authored by Ryan Day's avatar Ryan Day

adding wip parser refactor

parent fbc39a83
// STK message constants
module.exports.MESSAGE_START = '\x1B'
module.exports.TOKEN = '\x0E'
// STK general command constants
module.exports.CMD_SIGN_ON = '\x01'
module.exports.CMD_SET_PARAMETER = '\x02'
module.exports.CMD_GET_PARAMETER = '\x03'
module.exports.CMD_SET_DEVICE_PARAMETERS = '\x04'
module.exports.CMD_OSCCAL = '\x05'
module.exports.CMD_LOAD_ADDRESS = '\x06'
module.exports.CMD_FIRMWARE_UPGRADE = '\x07'
// STK ISP command constants
module.exports.CMD_ENTER_PROGMODE_ISP = '\x10'
module.exports.CMD_LEAVE_PROGMODE_ISP = '\x11'
module.exports.CMD_CHIP_ERASE_ISP = '\x12'
module.exports.CMD_PROGRAM_FLASH_ISP = '\x13'
module.exports.CMD_READ_FLASH_ISP = '\x14'
module.exports.CMD_PROGRAM_EEPROM_ISP = '\x15'
module.exports.CMD_READ_EEPROM_ISP = '\x16'
module.exports.CMD_PROGRAM_FUSE_ISP = '\x17'
module.exports.CMD_READ_FUSE_ISP = '\x18'
module.exports.CMD_PROGRAM_LOCK_ISP = '\x19'
module.exports.CMD_READ_LOCK_ISP = '\x1A'
module.exports.CMD_READ_SIGNATURE_ISP = '\x1B'
module.exports.CMD_READ_OSCCAL_ISP = '\x1C'
module.exports.CMD_SPI_MULTI = '\x1D'
// STK PP command constants
module.exports.CMD_ENTER_PROGMODE_PP = '\x20'
module.exports.CMD_LEAVE_PROGMODE_PP = '\x21'
module.exports.CMD_CHIP_ERASE_PP = '\x22'
module.exports.CMD_PROGRAM_FLASH_PP = '\x23'
module.exports.CMD_READ_FLASH_PP = '\x24'
module.exports.CMD_PROGRAM_EEPROM_PP = '\x25'
module.exports.CMD_READ_EEPROM_PP = '\x26'
module.exports.CMD_PROGRAM_FUSE_PP = '\x27'
module.exports.CMD_READ_FUSE_PP = '\x28'
module.exports.CMD_PROGRAM_LOCK_PP = '\x29'
module.exports.CMD_READ_LOCK_PP = '\x2A'
module.exports.CMD_READ_SIGNATURE_PP = '\x2B'
module.exports.CMD_READ_OSCCAL_PP = '\x2C'
module.exports.CMD_SET_CONTROL_STACK = '\x2D'
// STK HVSP command constants
module.exports.CMD_ENTER_PROGMODE_HVSP = '\x30'
module.exports.CMD_LEAVE_PROGMODE_HVSP = '\x31'
module.exports.CMD_CHIP_ERASE_HVSP = '\x32'
module.exports.CMD_PROGRAM_FLASH_HVSP = '\x33'
module.exports.CMD_READ_FLASH_HVSP = '\x34'
module.exports.CMD_PROGRAM_EEPROM_HVSP = '\x35'
module.exports.CMD_READ_EEPROM_HVSP = '\x36'
module.exports.CMD_PROGRAM_FUSE_HVSP = '\x37'
module.exports.CMD_READ_FUSE_HVSP = '\x38'
module.exports.CMD_PROGRAM_LOCK_HVSP = '\x39'
module.exports.CMD_READ_LOCK_HVSP = '\x3A'
module.exports.CMD_READ_SIGNATURE_HVSP = '\x3B'
module.exports.CMD_READ_OSCCAL_HVSP = '\x3C'
// STK status constants
// Success
module.exports.STATUS_CMD_OK = '\x00'
// Warnings
module.exports.STATUS_CMD_TOUT = '\x80'
module.exports.STATUS_RDY_BSY_TOUT = '\x81'
module.exports.STATUS_SET_PARAM_MISSING = '\x82'
// Errors
module.exports.STATUS_CMD_FAILED = '\xC0'
module.exports.STATUS_CKSUM_ERROR = '\xC1'
module.exports.STATUS_CMD_UNKNOWN = '\xC9'
// STK parameter constants
module.exports.STATUS_BUILD_NUMBER_LOW = '\x80'
module.exports.STATUS_BUILD_NUMBER_HIGH = '\x81'
module.exports.STATUS_HW_VER = '\x90'
module.exports.STATUS_SW_MAJOR = '\x91'
module.exports.STATUS_SW_MINOR = '\x92'
module.exports.STATUS_VTARGET = '\x94'
module.exports.STATUS_VADJUST = '\x95'
module.exports.STATUS_OSC_PSCALE = '\x96'
module.exports.STATUS_OSC_CMATCH = '\x97'
module.exports.STATUS_SCK_DURATION = '\x98'
module.exports.STATUS_TOPCARD_DETECT = '\x9A'
module.exports.STATUS_STATUS = '\x9C'
module.exports.STATUS_DATA = '\x9D'
module.exports.STATUS_RESET_POLARITY = '\x9E'
module.exports.STATUS_CONTROLLER_INIT = '\x9F'
// STK answer constants
module.exports.ANSWER_CKSUM_ERROR = '\xB0'
var c = require('./lib/constants');
var EventEmitter = require("events").EventEmitter;
module.exports = function(serialport){
var o = ext(
new EventEmitter,
{
constants:c,
port:serialport,
closed:false,
// write
_inc:-1,
_queue:[],
_current:false,
// parser.
states:["Start", "GetSequenceNumber", "GetMessageSize1", "GetMessageSize2", "GetToken", "GetData", "GetChecksum", "Done"],
state:0,
pkt:false,
// public interface,
send:function(body,cb){
if(this.closed) return setImmediate(function(){
var e = new Error('this parser is closed.');
e.code = "E_CLOSED";
cb(e);
});
if(!Buffer.isBuffer(body)) body = new Buffer(body);
var timeout = this._commandTimeout(body[0]);
var messageLen = new Buffer([0,0]);
messageLen.writeUInt16BE(body.length);
//MESSAGE_START,SEQUENCE_NUMBER,MESSAGE_SIZE,TOKEN,MESSAGE_BODY,CMD_READ/PROGRAM_FLASH/EEPROM,CHECKSUM
var out = Buffer.concat(new Buffer([c.MESSAGE_START,this._seq(),messageLen[0],messageLen[1],c.TOKEN]),body);
var checksum = this.checksum(out);
this.queue.push({buf:Buffer.concat(out,new Buffer([checksum])),seq:this._inc,cb:cb,timeout:timeout});
// if not waiting for another command to return. send this command
this._send();
},
checksum:function(buf){
var checksum = 0;
for(var i=0;i<buf.length;++i){
checksum ^= buf[i];
}
return checksum;
},
_seq:function(){
this._inc++;
if(this._inc > 0xff) this._inc = 0;
return this._inc;
},
_commandTimeout:function(typeByte){
//The total timeout period is from a command is sent to the answer must be completely
//received. The total timeout period is 200 ms for the CMD_SIGN_ON command, 5
//seconds for the CMD_READ/PROGRAM_FLASH/EEPROM commands, and 1
//second for all other commands.
timeout = 1000;
if(body[0] === c.CMD_SIGN_ON) timeout = 200;
else {
// grab the constant names.
var keys = Object.keys(c);
for(var i=0;i<keys.length;++i){
if(c[keys[i]] === typeByte) {
if(keys[i].indexOf('CMD_READ') > -1 || keys[i].indexOf('PROGRAM_FLASH') > -1 || keys[i].indexOf('EEPROM') > -1) {
timeout = 5000;
}
break;
}
}
}
return timeout;
},
_send:function(){
if(this.closed) return false;
if(this._current) return;
if(!this._queue.length) return;
var message = this._queue.shift();
var current = this._current = {
timeout:false,
seq:message.seq,
cb:message.cb
};
this._current
this.state = 0;
var z = this;
this.port.write(buf);
this.port.drain(function(){
if(current !== z._current) return z.emit('log',"current was no longer the current message after drain callback");
current.timeout = setTimeout(function(){
var err = new Error("stk500 timeout. "+message.timeout+"ms")
err.code = "E_TIMEOUT";
z._resolveCurrent(err);
},message.timeout);
});
this.emit('rawinput',buf);
},
_handle:function(data){
var current = this._current;
this.emit('raw',data);
if(!current) return this.emit('log','notice',"dropping data",'data');
// put state machine here. proove this works foolio.
for(var i=0;i<data.length;++i) {
this._stateMachine(data.readUInt8(i));
}
},
_pkt:function(){
return {
seq:-1,
len:[],
message:[],
checksum:0,
}
},
_stateMachine:function(curByte){
switch(state) {
case 0:
// always reset packet.
this.pkt = this._pkt();
if (curByte !== 0x1b) {
// the spec says "update statistics".
// the avrdude source just logs this out and does not treat it as a hard failure
// https://github.com/arduino/avrdude/blob/master/stk500v2.c#L399
return this.emit('log','parser',"Invalid header byte expected 27 got: " + curByte);
}
++this.state;
break;
case 1:
if (curByte !== this._current.seq) {
this.state = 0;
return this.emit('log','parser',"Invalid sequence number. back to start. got: " + curByte);
}
this.pkt.seq = curByte;
++this.state;
break;
case 2:
pkt.len.push(curByte);
++state;
break;
case 3:
pkt.len.push(curByte);
pkt.len = (pkt.messageLen[0] << 8) | pkt.messageLen[1];
++state;
break;
case 4:
if (curByte !== 0x0e) {
this.state = 0;
this.pkt.error = new Error("Invalid message token byte. got: " + curByte);
this.pkt.error.code = "E_PARSE";
return this.emit('log','parser',this.pkt.error);
}
++state;
// can stk500 send empty messages? probably not. avrdude doesnt support it.
if(!pkt.len) ++state;
break;
case 5:
if(pkt.len === 0 && curByte === c.STATUS_CKSUM_ERROR){
// the message was corrupted in transit or some such error.
// i could retry send the message for these errors!
// i dont buffer the message right now TODO
this.pkt.error = new Error("send checksum error");
this.pkt.error.code = "E_STATUS_CKSUM";
// TODO check to see if the first byte of all messages is a has errored byte.
// this checksum error is the only error checked for in avrdude source
}
pkt.message.push(curByte);
if (--pkt.len == 0) ++state;
break;
case 6:
pkt.checksum = this.checksum(this.pkt.message);
pkt.checksum = (pkt.checksum === curByte) ? true : false;
if(!pkt.checksum){
pkt.error = new Error("recv cecksum didn't match");
pkt.error.code = "E_RECV_CKSUM";
}
pkt.message = new Buffer(pkt.message);
this.emit('data',pkt);
this.state++;// sets state to 7. the parser is not interested in any other bytes until a message is queued.
this._resolveCurrent(s.pkt.error?this.pkt.error:false,pkt);
break;
}
},
_resolveCurrent:function(err,pkt){
var toCall = this._current;
this._current = false;
// write the next pending command.
// on timeout i error out the whole pending stack if any command errors.
// im fairly certain that this is the only responsible thing to do in the case of any parse error.
var q = false;
if(err.code == "E_PARSE" || err.code == "E_TIMEOUT" || this.closed){
q = this.queue;
this.queue = [];
}
toCall.cb(err,pkt);
if(q){
var e = err;
if(!this.closed) {
e = new Error("a call queued before this call errored leaving the protocol in an upredictable state. timidly refusing to run queued commands.");
e.code = "E_DEPENDENT";
e.prev_code = err.code;
}
while(q.lenth) q.shift()(err);
}
this._send();
}
});
serialPort.on('data',dataHandler).on('error',cleanup).on('close',cleanup);
return o;
function dataHandler(data){
o._handle(data);
};
function cleanup(err){
// prevent new commands from writing to serial.
o.closed = true;
// stop sending data
serialPort.removeListener('data',dataHandler);
serialPort.removeListener('error',cleanup);
serialPort.removeListener('close',cleanup);
if(!err) {
err = new Error('serial closed.');
err.code = "E_CLOSED";
}
if(o._current) o._resolveCurrent(err);
this.emit('closed');
}
}
function ext(o1,o2){
Object.keys(o2).forEach(function(k){
o1[k] = o2[k];
})
return o1;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment