jankolkmeier / xbee-api

Node.js and Chrome communicate with XBee/ZigBee devices in API mode
MIT License
105 stars 51 forks source link

RangeError: index out of range another case? #33

Closed emnik closed 9 years ago

emnik commented 9 years ago

Sorry for the bad title but... I am developing a smart thermostat using node.js on raspberry pi with Xbee Series 1. The following is happening only with xbee-api in node.js (in python code I don't have such a problem) so it must be a bug. The first time after booting RPi, on every Xbee frame I receive when I added console.log(util.inspect(S)); in xbee-api.js (~line 187) I get: MULTIPLE frames (every time much more than the previous) with wrong offset, length etc: To be more specific: I receive data from a remote Xbee every 30sec:

On the first data I get 16 TIMES:

{ buffer: <Buffer 00 00 a8 48 e4 01 00 00 00 00 48 34 e4 01 a0 a7 a1 be 00 00 00 00 ff ff ff ff 20 c4 81 00 00 00 00 00 00 00 00 00 60 00 00 00 2d 00 00 00 fc 48 e4 01 ... >,
  offset: 0,
  length: 0,
  total: 0,
  checksum: 0,
  b: 0,
  escape_next: false,
  waiting: true }

On the second data I get 11 TIMES:

{ buffer: <Buffer 7e 00 00 09 81 00 02 35 00 00 00 f0 41 16 00 00 00 74 6f 67 67 6c 65 00 00 00 c1 81 00 30 95 d7 01 01 13 05 02 60 13 05 02 06 00 00 00 0a 1e c8 d3 00 ... >,

with offset values ranging from 1 to 14 every time...

On the third data I get 13 TIMES:

{ buffer: <Buffer 7e 00 00 09 81 00 02 35 00 00 00 f0 41 16 7e 00 09 81 00 02 33 00 00 80 f0 41 98 81 00 30 95 d7 01 01 13 05 02 60 13 05 02 06 00 00 00 0a 1e c8 d3 00 ... >,

with offset values ranging from 5 to 27 every time...

... this keeps growing and when the offset becomes = 128 it crashes:

buffer.js:706
    throw new RangeError('index out of range');
          ^
RangeError: index out of range
    at checkInt (buffer.js:706:11)
    at Buffer.writeUInt8 (buffer.js:748:5)
    at XBeeAPI.parseRaw (/home/pi/apps/pyre/node_modules/xbee-api/lib/xbee-api.js:162:18)
    at XBeeAPI.<anonymous> (/home/pi/apps/pyre/node_modules/xbee-api/lib/xbee-api.js:133:10)
    at Object.SerialPort.opts.dataCallback (/home/pi/apps/pyre/node_modules/serialport/serialport.js:181:12)
    at SerialPortFactory.SerialPort._emitData (/home/pi/apps/pyre/node_modules/serialport/serialport.js:396:20)
    at afterRead (/home/pi/apps/pyre/node_modules/serialport/serialport.js:372:20)
    at /home/pi/apps/pyre/node_modules/serialport/serialport.js:388:9
    at FSReqWrap.wrapper [as oncomplete] (fs.js:529:17)
31 Jul 17:39:35 - [nodemon] app crashed - waiting for file changes before starting...

SEE HERE: http://pastebin.com/LSeRbJQm

Now, if I restart the app everything works just fine: Every 30 seconds I get a frame such:

{ buffer: <Buffer 7e 00 09 81 00 02 30 00 00 80 f0 41 9b 06 02 00 00 07 02 00 00 98 ef 0a 02 01 00 00 00 01 00 00 00 01 36 63 6e 08 02 00 00 00 00 00 00 b0 f2 0a 02 88 ... >,
  offset: 13,
  length: 9,
  total: 612,
  checksum: 155,
  b: 155,
  escape_next: false,
  waiting: false }
emnik commented 9 years ago

the code I use is:

var SerialPort = require('serialport').SerialPort;
var xbee_api = require('xbee-api');
var util = require('util');
var sqlite3 = require('sqlite3').verbose();
var db = new sqlite3.Database('./sensor-data.sqlite');

function readRemoteTemp(callback)
{
  var xbeeAPI = new xbee_api.XBeeAPI({
    api_mode: 1, //ATAP1 -without escape characters
    module: "802.15.4"
  });

  var serialport = new SerialPort("/dev/ttyAMA0", {
    baudrate: 9600,
    parser: xbeeAPI.rawParser()
  });

  xbeeAPI.on("frame_object", function(frame) {
    console.log('hello from frame_object');
    if (frame.remote16 === '0002') // this is the address of the remote Xbee
    {
      var buf = frame.data;
      var value = buf.readFloatLE(0);
      //console.log(value);
      var data = {
        temperature_record:[{
        unix_time: Date.now(),
        celsius: Math.round(value * 10) / 10 //round temperature to 1 decimal digit
        }]};
      // console.log(data);
      if (data.temperature_record[0].celsius!=""){
        callback(data);
      }
    }
  })
}

  // Write a single temperature record in JSON format to database table.
  function insertRemoteTemp(data){
     //var db = new sqlite3.Database('./sensor-data.sqlite');
     // data is a javascript object
     var statement = db.prepare("INSERT INTO sensor_data (timestamp, sensor_id, value) VALUES (?, 2, ?)");
     // Insert values into prepared statement
     statement.run(data.temperature_record[0].unix_time, data.temperature_record[0].celsius, function(error){
       if (error){
         console.log("SQL error from xbee module: ");
         console.log(error);
       }
     });
     // Execute the statement
     statement.finalize();
     //db.close();
  }

  function logRemoteTemp(){
    readRemoteTemp(insertRemoteTemp);
  }

module.exports.logRemoteTemp = logRemoteTemp;
jankolkmeier commented 9 years ago

Hey man, sorry you're having these problems. Could you log all the bytes received from the serial port to a file? (writing buffer[i] at the beginning of the for loop in parseRaw)

emnik commented 9 years ago

Ok, lets see: In

XBeeAPI.prototype.parseRaw = function(buffer) {
  var S = this.parseState;
  for(var i = 0; i < buffer.length; i++) {

I added:

console.log('logging buffer['+i+']: '+buffer[i]);
if (i==buffer.length-1) console.log('-----------------------');

and when I use the module I also added a consoloe.log(frame)

  xbeeAPI.on("frame_object", function(frame) {
    console.log(frame);

You can see the normal log at http://pastebin.com/jvfaKZ2P and the error log at http://pastebin.com/6FreaQ7i

if (S.length > 0 && S.offset === S.length + 4) {
...
this.emit("frame_object", frame);
...

is never true in that case. I saw that in the frames of the first message (http://pastebin.com/LSeRbJQm) I posted above...

I'll remind you that the error happens only the first time running the app after boot.

jankolkmeier commented 9 years ago

I think it is connected to all those zeros leaking in from the serial port, even once the xbee starts talking (see those extra zeros in between the message, line 19 to 32 in error.log?). I'm not sure if I should "fix" this on the xbee-api side, since this is clearly caused by the raspi...

It seems like the zeros stop leaking in after some time. Maybe you can work around that on your end? wait for xbee-api to be (re-)initialized?

emnik commented 9 years ago

OK... Any idea how to do this? I have tried catching the RangeError and I didn't succeed. At first I tried with try / catch and it didn't work because of the async nature of node... Then I read about domains and I tried them:

  var d = require('domain').create();
  d.on('error', function(err){
      // handle the error safely
      console.log('error caugth in domain...');
      console.log(err);
     //trying to re-initialize xbee-api:
      ds1820_xbee.logRemoteTemp(); //the code is shown in my second message above...
  });
  d.run(function(){
    process.nextTick(function(){
      ds1820_xbee.logRemoteTemp(); //the code is shown in my second message above...
    })
  });

but again it doesn't catch the RangeError... and crashes. I even used try/catch with domains together as I've read somewhere but that also didn't caught the error. If you have any better idea I'd be happy to learn from you :-)

emnik commented 9 years ago

So, I kind of solved (more of a hack really) my problem by encapsulating the for loop of the ParseRaw function in an:

if (buffer.length<=8) {
    the for loop...
}

I added this, as I noticed that on normal operation the buffer length is 8+5 (the for loop runs twice for every frame - Can you please explain to me why?), while when the error arrises, the buffer length at first is 16 (not ok) then 9 + 5 (not ok) and after that 8 + 5 / 8 + 5 / 8 + 5 /... that seems ok. Now I don't know why is this happening but for me seems to be working. Also I don't know if the values 8 followed by 5 for the buffer size, is specific to the data I am sending over RF (I send a float representing a thermal value) or not...

emnik commented 9 years ago

I'd like your opinion on this...

jankolkmeier commented 9 years ago

Yes, the 8+5 is specific to your data. Other messages will have other sizes. Also, the messages may come in chunks from the serial port (4 bytes, 2 bytes 10 bytes 1 byte) (and inside the for loop, we deal with each individual byte of that chunk). So the for loop may run for a single message several times (for the first 4 bytes, then for 5 more bytes, etc.)...

I can't test this fix, but maybe this could also work for you: #34