diff --git a/binding.gyp b/binding.gyp index 48491717d..58cffc081 100644 --- a/binding.gyp +++ b/binding.gyp @@ -4,7 +4,6 @@ 'target_name': 'serialport', 'sources': [ 'src/serialport.cpp', - 'src/serialport_unix.cpp', ], 'conditions': [ ['OS=="win"', @@ -20,6 +19,7 @@ { 'sources': [ 'src/serialport_unix.cpp', + 'src/serialport_poller.cpp', ], } ], diff --git a/serialport.js b/serialport.js index f6b5c82da..f7f287ac2 100644 --- a/serialport.js +++ b/serialport.js @@ -5,6 +5,7 @@ var Buffer = require('buffer').Buffer; var SerialPortBinding = require("bindings")("serialport.node"); +var EventEmitter = require('events').EventEmitter; var util = require('util'); var fs = require('fs'); var stream = require('stream'); @@ -12,6 +13,7 @@ var path = require('path'); var async = require('async'); var child_process = require('child_process'); + // Removing check for valid BaudRates due to ticket: #140 // var BAUDRATES = [500000, 230400, 115200, 57600, 38400, 19200, 9600, 4800, 2400, 1800, 1200, 600, 300, 200, 150, 134, 110, 75, 50]; @@ -23,6 +25,16 @@ var PARITY = ['none', 'even', 'mark', 'odd', 'space']; var FLOWCONTROLS = ["XON", "XOFF", "XANY", "RTSCTS"]; +// Stuff from ReadStream, refactored for our usage: +var kPoolSize = 40 * 1024; +var kMinPoolSpace = 128; +var pool; + +function allocNewPool() { + pool = new Buffer(kPoolSize); + pool.used = 0; +} + var parsers = { raw: function (emitter, buffer) { @@ -80,7 +92,7 @@ function SerialPort (path, options, openImmediately) { throw new Error('Invalid "databits": ' + options.dataBits); } - options.stopbits = options.stopBits || options.stopbits || _options.stopbits; + options.stopBits = options.stopBits || options.stopbits || _options.stopbits; if (STOPBITS.indexOf(options.stopBits) == -1) { throw new Error('Invalid "stopbits": ' + options.stopbits); } @@ -130,7 +142,13 @@ function SerialPort (path, options, openImmediately) { options.dataCallback = function (data) { options.parser(self, data); }; + + // options.dataReadyCallback = function () { + // self.readStream._read(4024); + // }; + options.errorCallback = function (err) { + // console.log("err:", JSON.stringify(err)); self.emit('error', err); }; options.disconnectedCallback = function () { @@ -138,11 +156,21 @@ function SerialPort (path, options, openImmediately) { return; } self.emit('error', new Error("Disconnected")); - self.close(); + // self.close(); }; if (process.platform == 'win32') { path = '\\\\.\\' + path; + } else { + // All other platforms: + this.fd = null; + this.paused = true; + this.bufferSize = options.bufferSize || 64 * 1024; + this.readable = true; + this.reading = false; + + if (options.encoding) + this.setEncoding(this.encoding); } this.options = options; @@ -167,15 +195,20 @@ SerialPort.prototype.open = function (callback) { return; } if (process.platform !== 'win32') { - self.readStream = fs.createReadStream(self.path, { bufferSize: self.options.bufferSize, fd: fd }); - self.readStream.on("data", self.options.dataCallback); - self.readStream.on("error", self.options.errorCallback); - self.readStream.on("close", function () { - self.close(); - }); - self.readStream.on("end", function () { - self.emit('end'); - }); + // self.readStream = new SerialStream(self.fd, { bufferSize: self.options.bufferSize }); + // self.readStream.on("data", self.options.dataCallback); + // self.readStream.on("error", self.options.errorCallback); + // self.readStream.on("close", function () { + // self.close(); + // }); + // self.readStream.on("end", function () { + // console.log(">>END"); + // self.emit('end'); + // }); + // self.readStream.resume(); + self.paused = false; + self.serialPoller = new SerialPortBinding.SerialportPoller(self.fd, function() {self._read();}); + self.serialPoller.start(); } self.emit('open'); @@ -206,11 +239,120 @@ SerialPort.prototype.write = function (buffer, callback) { }); }; +if (process.platform !== 'win32') { + SerialPort.prototype._read = function() { + var self = this; + + console.log(">>READ"); + if (!self.readable || self.paused || self.reading) return; + + self.reading = true; + + if (!pool || pool.length - pool.used < kMinPoolSpace) { + // discard the old pool. Can't add to the free list because + // users might have refernces to slices on it. + pool = null; + allocNewPool(); + } + + // Grab another reference to the pool in the case that while we're in the + // thread pool another read() finishes up the pool, and allocates a new + // one. + var thisPool = pool; + var toRead = Math.min(pool.length - pool.used, ~~self.bufferSize); + var start = pool.used; + + function afterRead(err, bytesRead, readPool, bytesRequested) { + self.reading = false; + if (err) { + if (err.code && err.code == 'EAGAIN') { + if (self.fd >= 0) + self.serialPoller.start(); + } else { + self.fd = null; + self.options.errorCallback(err); + self.readable = false; + return; + } + } + + // Since we will often not read the number of bytes requested, + // let's mark the ones we didn't need as available again. + pool.used -= bytesRequested - bytesRead; + + console.log(">>ACTUALLY READ: ", bytesRead); + + if (bytesRead === 0) { + if (self.fd >= 0) + self.serialPoller.start(); + } else { + var b = thisPool.slice(start, start + bytesRead); + + // do not emit events if the stream is paused + if (self.paused) { + self.buffer = Buffer.concat([self.buffer, b]); + return; + } else { + self._emitData(b); + } + + // do not emit events anymore after we declared the stream unreadable + if (!self.readable) return; + + self._read(); + } + } + + console.log(">>REQUEST READ: ", toRead); + fs.read(self.fd, pool, pool.used, toRead, self.pos, function(err, bytesRead){ + var readPool = pool; + var bytesRequested = toRead; + afterRead(err, bytesRead, readPool, bytesRequested);} + ); + + pool.used += toRead; + }; + + + SerialPort.prototype._emitData = function(d) { + var self = this; + // if (self._decoder) { + // var string = self._decoder.write(d); + // if (string.length) self.options.dataCallback(string); + // } else { + self.options.dataCallback(d); + // } + }; + + SerialPort.prototype.pause = function() { + var self = this; + self.paused = true; + }; + + + SerialPort.prototype.resume = function() { + var self = this; + self.paused = false; + + if (self.buffer) { + var buffer = self.buffer; + self.buffer = null; + self._emitData(buffer); + } + + // No longer open? + if (null == self.fd) + return; + + self._read(); + }; + +} // if !'win32' + SerialPort.prototype.close = function (callback) { var self = this; - var fd = this.fd; - this.fd = 0; + var fd = self.fd; if (self.closing) { return; @@ -226,6 +368,9 @@ SerialPort.prototype.close = function (callback) { self.closing = true; try { if (self.readStream) { + // Make sure we clear the readStream's fd, or it'll try to close() it. + // We already close()d it. + self.readStream.fd = null; self.readStream.destroy(); } @@ -239,6 +384,12 @@ SerialPort.prototype.close = function (callback) { self.emit('close'); self.removeAllListeners(); self.closing = false; + self.fd = 0; + + if (process.platform !== 'win32') { + self.readable = false; + self.serialPoller.close(); + } }); } catch (ex) { self.closing = false; @@ -315,7 +466,7 @@ if (process.platform === 'win32') { SerialPort.prototype.flush = function (callback) { var self = this; - var fd = this.fd; + var fd = self.fd; if (!fd) { if (callback) { diff --git a/src/serialport.cpp b/src/serialport.cpp index 68b611cc8..0d9805f24 100644 --- a/src/serialport.cpp +++ b/src/serialport.cpp @@ -4,6 +4,8 @@ #ifdef WIN32 #define strcasecmp stricmp +#else +#include "serialport_poller.h" #endif uv_mutex_t write_queue_mutex; @@ -341,6 +343,10 @@ extern "C" { NODE_SET_METHOD(target, "close", Close); NODE_SET_METHOD(target, "list", List); NODE_SET_METHOD(target, "flush", Flush); + +#ifndef WIN32 + SerialportPoller::Init(target); +#endif } } diff --git a/src/serialport_poller.cpp b/src/serialport_poller.cpp new file mode 100644 index 000000000..c3bd9e2d9 --- /dev/null +++ b/src/serialport_poller.cpp @@ -0,0 +1,105 @@ +// Copyright (C) 2013 Robert Giseburt +// serialport_poller.cpp Written as a part of https://github.com/voodootikigod/node-serialport +// License to use this is the same as that of node-serialport. + +#include +#include "serialport_poller.h" + +using namespace v8; + +SerialportPoller::SerialportPoller() : ObjectWrap() {}; +SerialportPoller::~SerialportPoller() { + // printf("~SerialportPoller\n"); + callback_.Dispose(); +}; + +void _serialportReadable(uv_poll_t *req, int status, int events) { + SerialportPoller* obj = (SerialportPoller*) req->data; + + // We can stop polling until we have read all of the data... + obj->_stop(); + + obj->callCallback(); +} + +void SerialportPoller::callCallback() { + // uv_work_t* req = new uv_work_t; + + // Call the callback to go read more data... + v8::Function::Cast(*callback_)->Call(v8::Context::GetCurrent()->Global(), 0, NULL);//2, argv +} + +void SerialportPoller::Init(Handle target) { + // Prepare constructor template + Local tpl = FunctionTemplate::New(New); + tpl->SetClassName(String::NewSymbol("SerialportPoller")); + tpl->InstanceTemplate()->SetInternalFieldCount(1); + + + // Prototype + + // SerialportPoller.close() + tpl->PrototypeTemplate()->Set(String::NewSymbol("close"), + FunctionTemplate::New(Close)->GetFunction()); + + // SerialportPoller.start() + tpl->PrototypeTemplate()->Set(String::NewSymbol("start"), + FunctionTemplate::New(Start)->GetFunction()); + + Persistent constructor = Persistent::New(tpl->GetFunction()); + target->Set(String::NewSymbol("SerialportPoller"), constructor); +} + +Handle SerialportPoller::New(const Arguments& args) { + HandleScope scope; + + SerialportPoller* obj = new SerialportPoller(); + + if(!args[0]->IsInt32()) { + return scope.Close(v8::ThrowException(v8::Exception::TypeError(v8::String::New("First argument must be an fd")))); + } + obj->fd_ = args[0]->ToInt32()->Int32Value(); + + if(!args[1]->IsFunction()) { + return scope.Close(v8::ThrowException(v8::Exception::TypeError(v8::String::New("Third argument must be a function")))); + } + obj->callback_ = v8::Persistent::New(v8::Local::Cast(args[1])); + // obj->callCallback(); + + obj->Wrap(args.This()); + + obj->poll_handle_.data = obj; +/*int r = */uv_poll_init(uv_default_loop(), &obj->poll_handle_, obj->fd_); + + uv_poll_start(&obj->poll_handle_, UV_READABLE, _serialportReadable); + + return args.This(); +} + +void SerialportPoller::_start() { + uv_poll_start(&poll_handle_, UV_READABLE, _serialportReadable); +} + +void SerialportPoller::_stop() { + uv_poll_stop(&poll_handle_); +} + + +Handle SerialportPoller::Start(const Arguments& args) { + HandleScope scope; + + SerialportPoller* obj = ObjectWrap::Unwrap(args.This()); + obj->_start(); + + return scope.Close(Undefined()); +} +Handle SerialportPoller::Close(const Arguments& args) { + HandleScope scope; + + SerialportPoller* obj = ObjectWrap::Unwrap(args.This()); + obj->_stop(); + + // DO SOMETHING! + + return scope.Close(Undefined()); +} \ No newline at end of file diff --git a/src/serialport_poller.h b/src/serialport_poller.h new file mode 100644 index 000000000..e4d1861e6 --- /dev/null +++ b/src/serialport_poller.h @@ -0,0 +1,33 @@ +// Copyright (C) 2013 Robert Giseburt +// serialport_poller.h Written as a part of https://github.com/voodootikigod/node-serialport +// License to use this is the same as that of node-serialport. + +#ifndef SERIALPORT_POLLER_H +#define SERIALPORT_POLLER_H + +#include + +class SerialportPoller : public node::ObjectWrap { + public: + static void Init(v8::Handle target); + + void callCallback(); + + void _start(); + void _stop(); + + private: + SerialportPoller(); + ~SerialportPoller(); + + static v8::Handle New(const v8::Arguments& args); + static v8::Handle Close(const v8::Arguments& args); + static v8::Handle Start(const v8::Arguments& args); + + uv_poll_t poll_handle_; + int fd_; + + v8::Persistent callback_; +}; + +#endif \ No newline at end of file diff --git a/src/serialport_unix.cpp b/src/serialport_unix.cpp index 90bec59d5..7191dfb2e 100644 --- a/src/serialport_unix.cpp +++ b/src/serialport_unix.cpp @@ -1,5 +1,6 @@ #ifndef WIN32 #include "serialport.h" +#include "serialport_poller.h" #include #include #include @@ -15,14 +16,19 @@ uv_mutex_t list_mutex; Boolean lockInitialised = FALSE; - #endif + #if defined(MAC_OS_X_VERSION_10_4) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4) #include #include #include #endif +#if defined(__linux__) +#include +#include +#endif + int ToBaudConstant(int baudRate); int ToDataBitsConstant(int dataBits); int ToStopBitsConstant(SerialPortStopBits stopBits); @@ -30,45 +36,45 @@ int ToStopBitsConstant(SerialPortStopBits stopBits); void AfterOpenSuccess(int fd, v8::Handle dataCallback, v8::Handle disconnectedCallback, v8::Handle errorCallback) { } -// Removing check for valid BaudRates due to ticket: #140 -// int ToBaudConstant(int baudRate) { -// switch (baudRate) { -// case 0: return B0; -// case 50: return B50; -// case 75: return B75; -// case 110: return B110; -// case 134: return B134; -// case 150: return B150; -// case 200: return B200; -// case 300: return B300; -// case 600: return B600; -// case 1200: return B1200; -// case 1800: return B1800; -// case 2400: return B2400; -// case 4800: return B4800; -// case 9600: return B9600; -// case 19200: return B19200; -// case 38400: return B38400; -// case 57600: return B57600; -// case 115200: return B115200; -// case 230400: return B230400; -// #if !defined(__APPLE__) && !defined(__OpenBSD__) -// case 460800: return B460800; -// case 500000: return B500000; -// case 576000: return B576000; -// case 921600: return B921600; -// case 1000000: return B1000000; -// case 1152000: return B1152000; -// case 1500000: return B1500000; -// case 2000000: return B2000000; -// case 2500000: return B2500000; -// case 3000000: return B3000000; -// case 3500000: return B3500000; -// case 4000000: return B4000000; -// #endif -// } -// return -1; -// } + +int ToBaudConstant(int baudRate) { + switch (baudRate) { + case 0: return B0; + case 50: return B50; + case 75: return B75; + case 110: return B110; + case 134: return B134; + case 150: return B150; + case 200: return B200; + case 300: return B300; + case 600: return B600; + case 1200: return B1200; + case 1800: return B1800; + case 2400: return B2400; + case 4800: return B4800; + case 9600: return B9600; + case 19200: return B19200; + case 38400: return B38400; + case 57600: return B57600; + case 115200: return B115200; + case 230400: return B230400; +#if !defined(__APPLE__) && !defined(__OpenBSD__) + case 460800: return B460800; + case 500000: return B500000; + case 576000: return B576000; + case 921600: return B921600; + case 1000000: return B1000000; + case 1152000: return B1152000; + case 1500000: return B1500000; + case 2000000: return B2000000; + case 2500000: return B2500000; + case 3000000: return B3000000; + case 3500000: return B3500000; + case 4000000: return B4000000; +#endif + } + return -1; +} #ifdef __APPLE__ typedef struct SerialDevice { @@ -98,17 +104,18 @@ int ToDataBitsConstant(int dataBits) { } + void EIO_Open(uv_work_t* req) { OpenBaton* data = static_cast(req->data); - int baudRate = data->baudRate; - // #if not ( defined(MAC_OS_X_VERSION_10_4) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4) ) - // Removing check for valid BaudRates due to ticket: #140 - // if(baudRate == -1) { - // snprintf(data->errorString, sizeof(data->errorString), "Invalid baud rate setting %d", data->baudRate); - // return; - // } - // #endif + int baudRate = ToBaudConstant(data->baudRate); + +// #if not ( defined(MAC_OS_X_VERSION_10_4) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4) ) +// if(baudRate == -1) { +// snprintf(data->errorString, sizeof(data->errorString), "Invalid baud rate setting %d", data->baudRate); +// return; +// } +// #endif int dataBits = ToDataBitsConstant(data->dataBits); if(dataBits == -1) { @@ -124,39 +131,75 @@ void EIO_Open(uv_work_t* req) { snprintf(data->errorString, sizeof(data->errorString), "Cannot open %s", data->path); return; } + - struct termios options; - struct sigaction saio; - saio.sa_handler = SIG_IGN; - sigemptyset(&saio.sa_mask); - saio.sa_flags = 0; - sigaction(SIGIO, &saio, NULL); + // struct sigaction saio; + // saio.sa_handler = sigio_handler; + // sigemptyset(&saio.sa_mask); + // saio.sa_flags = 0; + // sigaction(SIGIO, &saio, NULL); - //all process to receive SIGIO - fcntl(fd, F_SETOWN, getpid()); - fcntl(fd, F_SETFL, FASYNC); + // //all process to receive SIGIO + // fcntl(fd, F_SETOWN, getpid()); + // int flflags = fcntl(fd, F_GETFL); + // fcntl(fd, F_SETFL, flflags | FNONBLOCK); + struct termios options; // Set baud and other configuration. tcgetattr(fd, &options); + // Removing check for valid BaudRates due to ticket: #140 // #if not ( defined(MAC_OS_X_VERSION_10_4) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4) ) // Specify the baud rate - cfsetispeed(&options, baudRate); - cfsetospeed(&options, baudRate); + + + // On linux you can alter the meaning of B38400 to mean a custom baudrate... +#if defined(__linux__) && defined(ASYNC_SPD_CUST) + if (baudRate == -1) { + struct serial_struct serinfo; + serinfo.reserved_char[0] = 0; + if (ioctl(fd, TIOCGSERIAL, &serinfo) != -1) { + serinfo.flags &= ~ASYNC_SPD_MASK; + serinfo.flags |= ASYNC_SPD_CUST; + serinfo.custom_divisor = (serinfo.baud_base + (data->baudRate / 2)) / data->baudRate; + if (serinfo.custom_divisor < 1) + serinfo.custom_divisor = 1; + + ioctl(fd, TIOCSSERIAL, &serinfo); + ioctl(fd, TIOCGSERIAL, &serinfo); + // if (serinfo.custom_divisor * rate != serinfo.baud_base) { + // warnx("actual baudrate is %d / %d = %f", + // serinfo.baud_base, serinfo.custom_divisor, + // (float)serinfo.baud_base / serinfo.custom_divisor); + // } + } + + // Now we use "B38400" to trigger the special baud rate. + baudRate = B38400; + } +#endif + + if (baudRate != -1) { + cfsetispeed(&options, baudRate); + cfsetospeed(&options, baudRate); + } + // Removing check for valid BaudRates due to ticket: #140 // #endif - /* IGNPAR : ignore bytes with parity errors + */ + options.c_iflag = IGNPAR; + + /* ICRNL : map CR to NL (otherwise a CR input on the other computer will not terminate input) - otherwise make device raw (no other input processing) */ - options.c_iflag = IGNPAR | ICRNL; - - + // Pulling this for now. It should be an option, however. -Giseburt + //options.c_iflag = ICRNL; + // otherwise make device raw (no other input processing) // Specify data bits @@ -229,12 +272,13 @@ void EIO_Open(uv_work_t* req) { options.c_cflag |= CLOCAL; //ignore status lines options.c_cflag |= CREAD; //enable receiver options.c_cflag |= HUPCL; //drop DTR (i.e. hangup) on close - // options.c_iflag = IGNPAR; // Raw output options.c_oflag = 0; - options.c_lflag = ICANON; //0; + // ICANON makes partial lines not readable. It should be otional. + // It works with ICRNL. -Giseburt + options.c_lflag = 0; //ICANON; options.c_cc[VMIN]=1; options.c_cc[VTIME]=0; @@ -243,13 +287,15 @@ void EIO_Open(uv_work_t* req) { tcflush(fd, TCIFLUSH); tcsetattr(fd, TCSANOW, &options); - + // On OS X, starting in Tiger, we can set a custom baud rate, as follows: #if defined(MAC_OS_X_VERSION_10_4) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4) + if (baudRate == -1) { speed_t speed = data->baudRate; if (ioctl(fd, IOSSIOSPEED, &speed) == -1) { - snprintf(data->errorString, sizeof(data->errorString), "Error %s calling ioctl( ..., IOSSIOSPEED, ... )", strerror(errno) ); - } -#endif + snprintf(data->errorString, sizeof(data->errorString), "Error %s calling ioctl( ..., IOSSIOSPEED, %ld )", strerror(errno), speed ); + } + } +#endif data->result = fd; } @@ -266,7 +312,18 @@ void EIO_Write(uv_work_t* req) { void EIO_Close(uv_work_t* req) { CloseBaton* data = static_cast(req->data); - close(data->fd); + // printf(">>>> close fd %d\n", data->fd); + + // fcntl(data->fd, F_SETFL, FNONBLOCK); + + ssize_t r; + + r = close(data->fd); + + // printf(">>>> closed fd %d (err: %d)\n", data->fd, errno); + + if (r && r != EBADF) + snprintf(data->errorString, sizeof(data->errorString), "Unable to close fd %d, errno: %d", data->fd, errno); } #ifdef __APPLE__