Skip to content

Commit

Permalink
feat(linux): Custom baud rates for linux (eg 250k baudrate) (#1464)
Browse files Browse the repository at this point in the history
* 250k baudrate now works
* Generic linux non-standard baudrate setting using termios2
* Tested successfully with an FTDI usb chip with the tx and rx pins bridged. Previous solution using the B38400 hack returned gibberish in same setup.
* Test for linux and win32 baudrate setting. Darwin stubbed out.

The arbitrary baud interface has been working on darwin up to this point so it's
likely that it works properly. The GetBaudRate extension should still get
written to be thorough but for now, this will let the CI tests pass properly and
still throw a warning about the check being disabled.

That is, of course, if anyone's testing with the arduino hardware on darwin,
otherwise, these tests never run anyway.
  • Loading branch information
Fumon authored and reconbot committed Feb 5, 2018
1 parent bac94a0 commit 910438c
Show file tree
Hide file tree
Showing 14 changed files with 223 additions and 35 deletions.
9 changes: 9 additions & 0 deletions binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@
}
}
],
['OS=="linux"',
{
'sources': [
'src/serialport_unix.cpp',
'src/poller.cpp',
'src/serialport_linux.cpp'
]
}
],
['OS!="win"',
{
'sources': [
Expand Down
14 changes: 14 additions & 0 deletions lib/bindings/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions lib/bindings/darwin.js
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
5 changes: 5 additions & 0 deletions lib/bindings/linux.js
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
10 changes: 10 additions & 0 deletions lib/bindings/mock.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions lib/bindings/win32.js
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
48 changes: 48 additions & 0 deletions src/serialport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<int>(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<v8::Function>());

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<GetBaudRateBaton*>(req->data);

v8::Local<v8::Value> argv[2];

if (data->errorString[0]) {
argv[0] = v8::Exception::Error(Nan::New<v8::String>(data->errorString).ToLocalChecked());
argv[1] = Nan::Undefined();
} else {
v8::Local<v8::Object> results = Nan::New<v8::Object>();
results->Set(Nan::New<v8::String>("baudRate").ToLocalChecked(), Nan::New<v8::Integer>(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()) {
Expand Down Expand Up @@ -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);
Expand Down
11 changes: 11 additions & 0 deletions src/serialport.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
38 changes: 38 additions & 0 deletions src/serialport_linux.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#if defined(__linux__)

#include <sys/ioctl.h>
#include <asm/termbits.h>

// 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
8 changes: 8 additions & 0 deletions src/serialport_linux.h
Original file line number Diff line number Diff line change
@@ -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

44 changes: 29 additions & 15 deletions src/serialport_unix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#if defined(__linux__)
#include <sys/ioctl.h>
#include <linux/serial.h>
#include "serialport_linux.h"
#endif

int ToStopBitsConstant(SerialPortStopBits stopBits);
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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<GetBaudRateBaton*>(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<VoidBaton*>(req->data);

Expand Down
15 changes: 15 additions & 0 deletions src/serialport_win.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<GetBaudRateBaton*>(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<int>::iterator it = g_closingHandles.begin(); it != g_closingHandles.end(); ++it) {
if (fd == *it) {
Expand Down
6 changes: 6 additions & 0 deletions test-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
40 changes: 20 additions & 20 deletions test/bindings.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
});
});
});

Expand Down

0 comments on commit 910438c

Please sign in to comment.