Loading lib/constants-v2.js 0 → 100644 +92 −0 Original line number Diff line number Diff line // 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' lib/parser-v2.js 0 → 100644 +255 −0 Original line number Diff line number Diff line 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; } Loading
lib/constants-v2.js 0 → 100644 +92 −0 Original line number Diff line number Diff line // 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'
lib/parser-v2.js 0 → 100644 +255 −0 Original line number Diff line number Diff line 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; }