diff --git a/binding.gyp b/binding.gyp index 56e6e49fa..4bb48e92a 100644 --- a/binding.gyp +++ b/binding.gyp @@ -35,6 +35,15 @@ } } ], + ['OS=="linux"', + { + 'sources': [ + 'src/serialport_unix.cpp', + 'src/poller.cpp', + 'src/serialport_linux.cpp' + ] + } + ], ['OS!="win"', { 'sources': [ diff --git a/lib/bindings/base.js b/lib/bindings/base.js index 864c47849..39fa81a74 100644 --- a/lib/bindings/base.js +++ b/lib/bindings/base.js @@ -187,6 +187,20 @@ The in progress writes must error when the port is closed with an error object t return Promise.resolve(); } + /** + * Get the OS reported baud rate for the open port. + * Used mostly for debugging custom baud rates. + * @returns {Promise} Resolves with the current baud rate. + * @throws {TypeError} When given invalid arguments, a `TypeError` is thrown. + */ + getBaudRate() { + debug('getBuadRate'); + if (!this.isOpen) { + return Promise.reject(new Error('Port is not open')); + } + return Promise.resolve(); + } + /** * Flush (discard) data received but not read, and written but not transmitted. * @returns {Promise} Resolves once the flush operation finishes. diff --git a/lib/bindings/darwin.js b/lib/bindings/darwin.js index 7571d3448..95fda971e 100644 --- a/lib/bindings/darwin.js +++ b/lib/bindings/darwin.js @@ -80,6 +80,11 @@ class DarwinBinding extends BaseBinding { .then(() => promisify(binding.get)(this.fd)); } + getBaudRate() { + return super.get() + .then(() => promisify(binding.getBaudRate)(this.fd)); + } + drain() { return super.drain() .then(() => Promise.resolve(this.writeOperation)) diff --git a/lib/bindings/linux.js b/lib/bindings/linux.js index f22f1a9fa..0fc52d63c 100644 --- a/lib/bindings/linux.js +++ b/lib/bindings/linux.js @@ -81,6 +81,11 @@ class LinuxBinding extends BaseBinding { .then(() => promisify(binding.get)(this.fd)); } + getBaudRate() { + return super.getBaudRate() + .then(() => promisify(binding.getBaudRate)(this.fd)); + } + drain() { return super.drain() .then(() => Promise.resolve(this.writeOperation)) diff --git a/lib/bindings/mock.js b/lib/bindings/mock.js index 463435386..32f943364 100644 --- a/lib/bindings/mock.js +++ b/lib/bindings/mock.js @@ -206,6 +206,16 @@ class MockBinding extends BaseBinding { }); } + getBaudRate() { + return super.getBaudRate() + .then(resolveNextTick) + .then(() => { + return { + baudRate: this.port.openOpt.baudRate + }; + }); + } + flush() { return super.flush() .then(resolveNextTick) diff --git a/lib/bindings/win32.js b/lib/bindings/win32.js index 4f0a39563..77f152d74 100644 --- a/lib/bindings/win32.js +++ b/lib/bindings/win32.js @@ -86,6 +86,11 @@ class WindowsBinding extends BaseBinding { .then(() => promisify(binding.get)(this.fd)); } + getBaudRate() { + return super.get() + .then(() => promisify(binding.getBaudRate)(this.fd)); + } + drain() { return super.drain() .then(() => Promise.resolve(this.writeOperation)) diff --git a/src/serialport.cpp b/src/serialport.cpp index 34cee9ab7..a2bc28204 100644 --- a/src/serialport.cpp +++ b/src/serialport.cpp @@ -339,6 +339,53 @@ void EIO_AfterGet(uv_work_t* req) { delete req; } +NAN_METHOD(GetBaudRate) { + // file descriptor + if (!info[0]->IsInt32()) { + Nan::ThrowTypeError("First argument must be an int"); + return; + } + int fd = Nan::To(info[0]).FromJust(); + + // callback + if (!info[1]->IsFunction()) { + Nan::ThrowTypeError("Second argument must be a function"); + return; + } + + GetBaudRateBaton* baton = new GetBaudRateBaton(); + baton->fd = fd; + baton->baudRate = 0; + baton->callback.Reset(info[1].As()); + + uv_work_t* req = new uv_work_t(); + req->data = baton; + uv_queue_work(uv_default_loop(), req, EIO_GetBaudRate, (uv_after_work_cb)EIO_AfterGetBaudRate); +} + +void EIO_AfterGetBaudRate(uv_work_t* req) { + Nan::HandleScope scope; + + GetBaudRateBaton* data = static_cast(req->data); + + v8::Local argv[2]; + + if (data->errorString[0]) { + argv[0] = v8::Exception::Error(Nan::New(data->errorString).ToLocalChecked()); + argv[1] = Nan::Undefined(); + } else { + v8::Local results = Nan::New(); + results->Set(Nan::New("baudRate").ToLocalChecked(), Nan::New(data->baudRate)); + + argv[0] = Nan::Null(); + argv[1] = results; + } + data->callback.Call(2, argv); + + delete data; + delete req; +} + NAN_METHOD(Drain) { // file descriptor if (!info[0]->IsInt32()) { @@ -414,6 +461,7 @@ extern "C" { Nan::HandleScope scope; Nan::SetMethod(target, "set", Set); Nan::SetMethod(target, "get", Get); + Nan::SetMethod(target, "getBaudRate", GetBaudRate); Nan::SetMethod(target, "open", Open); Nan::SetMethod(target, "update", Update); Nan::SetMethod(target, "close", Close); diff --git a/src/serialport.h b/src/serialport.h index 63e1e5cbf..32bd1b076 100644 --- a/src/serialport.h +++ b/src/serialport.h @@ -32,6 +32,10 @@ NAN_METHOD(Get); void EIO_Get(uv_work_t* req); void EIO_AfterGet(uv_work_t* req); +NAN_METHOD(GetBaudRate); +void EIO_GetBaudRate(uv_work_t* req); +void EIO_AfterGetBaudRate(uv_work_t* req); + NAN_METHOD(Drain); void EIO_Drain(uv_work_t* req); void EIO_AfterDrain(uv_work_t* req); @@ -104,6 +108,13 @@ struct GetBaton { bool dcd; }; +struct GetBaudRateBaton { + int fd; + Nan::Callback callback; + char errorString[ERROR_STRING_SIZE]; + int baudRate; +}; + struct VoidBaton { int fd; Nan::Callback callback; diff --git a/src/serialport_linux.cpp b/src/serialport_linux.cpp new file mode 100755 index 000000000..328b0fa95 --- /dev/null +++ b/src/serialport_linux.cpp @@ -0,0 +1,38 @@ +#if defined(__linux__) + +#include +#include + +// Uses the termios2 interface to set nonstandard baud rates +int linuxSetCustomBaudRate(const int fd, const unsigned int baudrate) { + struct termios2 t; + + if(ioctl(fd, TCGETS2, &t)) { + return -1; + } + + t.c_cflag &= ~CBAUD; + t.c_cflag |= BOTHER; + t.c_ospeed = t.c_ispeed = baudrate; + + if(ioctl(fd, TCSETS2, &t)) { + return -2; + } + + return 0; +} + +// Uses termios2 interface to retrieve system reported baud rate +int linuxGetSystemBaudRate(const int fd, int* const outbaud) { + struct termios2 t; + + if(ioctl(fd, TCGETS2, &t)) { + return -1; + } + + *outbaud = (int)t.c_ospeed; + + return 0; +} + +#endif diff --git a/src/serialport_linux.h b/src/serialport_linux.h new file mode 100755 index 000000000..0388ac321 --- /dev/null +++ b/src/serialport_linux.h @@ -0,0 +1,8 @@ +#ifndef CUSTOM_BAUDRATE_H +#define CUSTOM_BAUDRATE_H + +int linuxSetCustomBaudRate(const int fd, const unsigned int baudrate); +int linuxGetSystemBaudRate(const int fd, int* const outbaud); + +#endif + diff --git a/src/serialport_unix.cpp b/src/serialport_unix.cpp index e1af0dd2c..3085c6861 100644 --- a/src/serialport_unix.cpp +++ b/src/serialport_unix.cpp @@ -24,6 +24,7 @@ #if defined(__linux__) #include #include +#include "serialport_linux.h" #endif int ToStopBitsConstant(SerialPortStopBits stopBits); @@ -111,24 +112,17 @@ int setBaudRate(ConnectionOptionsBaton *data) { // If there is a custom baud rate on linux you can do the following trick with B38400 #if defined(__linux__) && defined(ASYNC_SPD_CUST) if (baudRate == -1) { - struct serial_struct serinfo; - serinfo.reserved_char[0] = 0; - if (-1 != ioctl(fd, TIOCGSERIAL, &serinfo)) { - 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); - } else { - snprintf(data->errorString, sizeof(data->errorString), "Error: %s setting custom baud rate of %d", strerror(errno), data->baudRate); + int err = linuxSetCustomBaudRate(fd, data->baudRate); + + if (err == -1) { + snprintf(data->errorString, sizeof(data->errorString), "Error: %s || while retrieving termios2 info", strerror(errno)); + return -1; + } else if(err == -2) { + snprintf(data->errorString, sizeof(data->errorString), "Error: %s || while setting custom baud rate of %d", strerror(errno), data->baudRate); return -1; } - // Now we use "B38400" to trigger the special baud rate. - baudRate = B38400; + return 1; } #endif @@ -365,6 +359,26 @@ void EIO_Get(uv_work_t* req) { data->dcd = bits & TIOCM_CD; } +void EIO_GetBaudRate(uv_work_t* req) { + GetBaudRateBaton* data = static_cast(req->data); + int outbaud; + + #if defined(__linux__) && defined(ASYNC_SPD_CUST) + if (-1 == linuxGetSystemBaudRate(data->fd, &outbaud)) { + snprintf(data->errorString, sizeof(data->errorString), "Error: %s, cannot get baud rate", strerror(errno)); + return; + } + #endif + + // TODO implement on mac + #if defined(MAC_OS_X_VERSION_10_4) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4) + snprintf(data->errorString, sizeof(data->errorString), "Error: System baud rate check not implemented on darwin"); + return; + #endif + + data->baudRate = outbaud; +} + void EIO_Flush(uv_work_t* req) { VoidBaton* data = static_cast(req->data); diff --git a/src/serialport_win.cpp b/src/serialport_win.cpp index bc7ca649a..8ac6335b5 100644 --- a/src/serialport_win.cpp +++ b/src/serialport_win.cpp @@ -258,6 +258,21 @@ void EIO_Get(uv_work_t* req) { data->dcd = bits & MS_RLSD_ON; } +void EIO_GetBaudRate(uv_work_t* req) { + GetBaudRateBaton* data = static_cast(req->data); + + DCB dcb = { 0 }; + SecureZeroMemory(&dcb, sizeof(DCB)); + dcb.DCBlength = sizeof(DCB); + + if (!GetCommState((HANDLE)data->fd, &dcb)) { + ErrorCodeToString("Getting baud rate (GetCommState)", GetLastError(), data->errorString); + return; + } + + data->baudRate = (int)dcb.BaudRate; +} + bool IsClosingHandle(int fd) { for (std::list::iterator it = g_closingHandles.begin(); it != g_closingHandles.end(); ++it) { if (fd == *it) { diff --git a/test-config.json b/test-config.json index 5a89872ce..69ba436f7 100644 --- a/test-config.json +++ b/test-config.json @@ -3,6 +3,12 @@ "win32": { "open.unlock": false, "baudrate.25000": false, + "baudrate.25000_check": false, "port.update-baudrate": false + }, + "darwin": { + "baudrate.25000_check": false, + "baudrate.1000000_check": false, + "baudrate.250000_check": false } } diff --git a/test/bindings.js b/test/bindings.js index ed6cf3cc3..823b33a5f 100644 --- a/test/bindings.js +++ b/test/bindings.js @@ -210,27 +210,27 @@ function testBinding(bindingName, Binding, testPort) { }); }); - testFeature('baudrate.25000', 'supports a custom baudRate of 25000', () => { - const customRates = Object.assign({}, defaultOpenOptions, { baudRate: 25000 }); - return binding.open(testPort, customRates).then(() => { - assert.equal(binding.isOpen, true); - return binding.close(); - }); - }); - - testFeature('baudrate.1000000', 'supports a custom baudRate of 1000000', () => { - const customRates = Object.assign({}, defaultOpenOptions, { baudRate: 1000000 }); - return binding.open(testPort, customRates).then(() => { - assert.equal(binding.isOpen, true); - return binding.close(); - }); - }); + describe('arbitrary baud rates', () => { + [25000, 1000000, 250000].forEach((testBaud) => { + describe(`${testBaud} baud`, () => { + const customRates = Object.assign({}, defaultOpenOptions, { baudRate: testBaud }); + testFeature(`baudrate.${testBaud}`, `opens at ${testBaud} baud`, () => { + return binding.open(testPort, customRates) + .then(() => { + assert.equal(binding.isOpen, true); + return binding.close(); + }); + }); - testFeature('baudrate.250000', 'supports a custom baudRate of 1000000', () => { - const customRates = Object.assign({}, defaultOpenOptions, { baudRate: 250000 }); - return binding.open(testPort, customRates).then(() => { - assert.equal(binding.isOpen, true); - return binding.close(); + testFeature(`baudrate.${testBaud}_check`, `sets ${testBaud} baud successfully`, () => { + return binding.open(testPort, customRates) + .then(() => binding.getBaudRate()) + .then((res) => { + assert.equal(res.baudRate, customRates.baudRate); + return binding.close(); + }); + }); + }); }); });