diff --git a/README.md b/README.md index 881b08d..1d4ff13 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,44 @@ var bitmodes = { */ ``` +## Modem / Line status flags + +To manually get the modem and line status values: + +```nodejs +device.modemStatus(function(err, status) { + console.log(status); +}); +``` + +Or, to be notified anytime the change on an openned device: + +```nodejs +device.on('modemStatus', function(status) { + console.log(status) +}); +``` + +An example of the status object returned: + +```js +{ + raw: 176, // the raw value received from the FTDI library + + // Modem status + cts: true, // Clear To Send + dsr: true, // Data Set Ready + ri: false, // Ring Indicator + dcd: true, // Data Carrier Detect + + // Line status + oe: false, // Overrun Error + pe: false, // Parity Error + fe: false, // Framing Error + bi: false // Break Interrupt +} +``` + # Troubleshoot ### Windows diff --git a/index.js b/index.js index 447aad3..cc070e7 100644 --- a/index.js +++ b/index.js @@ -75,6 +75,8 @@ FtdiDevice.prototype.open = function(settings, callback) { self.emit('open'); } if (callback) { callback(err); } + }, function(err, modemStatus) { + self.emit('modemStatus', modemStatus); }); }; @@ -122,6 +124,20 @@ FtdiDevice.prototype.close = function(callback) { }); }; +/** + * Get the modem & line status flags + * @param {Function} callback The function, that will be called when the flags have been retreived. + * `function(err, flags){}` + */ +FtdiDevice.prototype.modemStatus = function(callback) { + this.FTDIDevice.modemStatus(function(err, status) { + if (err) { + self.emit('error', err); + } + if (callback) callback(err, status); + }); +}; + module.exports = { FtdiDevice: FtdiDevice, diff --git a/src/ftdi_constants.h b/src/ftdi_constants.h index 5dee63f..a1d82c4 100644 --- a/src/ftdi_constants.h +++ b/src/ftdi_constants.h @@ -43,6 +43,16 @@ #define FT_STATUS_CUSTOM_ALREADY_OPEN "Device Already open" #define FT_STATUS_CUSTOM_ALREADY_CLOSING "Device Already closing" +#define FT_MODEM_CTS 0x10 +#define FT_MODEM_DSR 0x20 +#define FT_MODEM_RI 0x40 +#define FT_MODEM_DCD 0x80 + +#define FT_LINE_OE 0x02 +#define FT_LINE_PE 0x04 +#define FT_LINE_FE 0x08 +#define FT_LINE_BI 0x10 + // Lock for Library Calls extern uv_mutex_t libraryMutex; extern uv_mutex_t vidPidMutex; diff --git a/src/ftdi_device.cc b/src/ftdi_device.cc index 89f550d..4db4207 100644 --- a/src/ftdi_device.cc +++ b/src/ftdi_device.cc @@ -37,6 +37,7 @@ using namespace ftdi_device; #define JS_CLASS_NAME "FtdiDevice" #define JS_WRITE_FUNCTION "write" #define JS_OPEN_FUNCTION "open" +#define JS_MODEM_STATUS_FUNCTION "modemStatus" #define JS_CLOSE_FUNCTION "close" @@ -47,6 +48,8 @@ UCHAR GetWordLength(int wordLength); UCHAR GetStopBits(int stopBits); UCHAR GetParity(const char* string); +bool checkFlag(DWORD val, int mask); + void ToCString(Local val, char ** ptr); @@ -280,14 +283,146 @@ FT_STATUS FtdiDevice::ReadDataAsync(FtdiDevice* device, ReadBaton_t* baton) } } +/***************************** + * MODEM Section + *****************************/ +class ModemStatusWorker : public Nan::AsyncWorker { + public: + ModemStatusWorker(Nan::Callback *callback, FtdiDevice* device, bool callbackLoop) + : Nan::AsyncWorker(callback), device(device), callbackLoop(callbackLoop), lastStatus(0) {} + ~ModemStatusWorker() {} + + // Executed inside the worker-thread. + // It is not safe to access V8, or V8 data structures + // here, so everything we need for input and output + // should go on `this`. + void Execute () { + baton = new ModemBaton_t(); + baton->status = (DWORD)0; + status = FtdiDevice::ModemStatusAsync(device, baton); + } + + void WorkComplete () { + Nan::HandleScope scope; + + if (ErrorMessage() == NULL) + { + HandleOKCallback(); + } + else + { + HandleErrorCallback(); + } + } + + // Executed when the async work is complete + // this function will be run inside the main event loop + // so it is safe to use V8 again + void HandleOKCallback () { + Nan::HandleScope scope; + + // If we're in a callback loop, only call callback if the status is different + if(callback != NULL && (callbackLoop == false || lastStatus != baton->status)) + { + Local argv[2]; + if(status != FT_OK) + { + argv[0] = Nan::New(GetStatusString(status)).ToLocalChecked(); + argv[1] = Local(Nan::New(0)); + } + else + { + argv[0] = Local(Nan::Undefined()); + + // Create return object + v8::Local resultObj = Nan::New(); + resultObj->Set( + Nan::New("raw").ToLocalChecked(), + Nan::New(baton->status)); + + DWORD modemStatus = (baton->status & 0x000000FF); + resultObj->Set( + Nan::New("cts").ToLocalChecked(), + Nan::New(checkFlag(modemStatus, FT_MODEM_CTS))); + resultObj->Set( + Nan::New("dsr").ToLocalChecked(), + Nan::New(checkFlag(modemStatus, FT_MODEM_DSR))); + resultObj->Set( + Nan::New("ri").ToLocalChecked(), + Nan::New(checkFlag(modemStatus, FT_MODEM_RI))); + resultObj->Set( + Nan::New("dcd").ToLocalChecked(), + Nan::New(checkFlag(modemStatus, FT_MODEM_DCD))); + + DWORD lineStatus = ((baton->status >> 8) & 0x000000FF); + resultObj->Set( + Nan::New("oe").ToLocalChecked(), + Nan::New(checkFlag(lineStatus, FT_LINE_OE))); + resultObj->Set( + Nan::New("pe").ToLocalChecked(), + Nan::New(checkFlag(lineStatus, FT_LINE_PE))); + resultObj->Set( + Nan::New("fe").ToLocalChecked(), + Nan::New(checkFlag(lineStatus, FT_LINE_FE))); + resultObj->Set( + Nan::New("bi").ToLocalChecked(), + Nan::New(checkFlag(lineStatus, FT_LINE_BI))); + + argv[1] = resultObj; + } + + callback->Call(2, argv); + } + lastStatus = baton->status; + + if (callbackLoop) { + AsyncQueueWorkerPersistent(this); + } + }; + + private: + FT_STATUS status; + FtdiDevice* device; + ModemBaton_t* baton; + bool callbackLoop; + DWORD lastStatus; +}; + +NAN_METHOD(FtdiDevice::ModemStatus) { + Nan::HandleScope scope; + + Nan::Callback *callback = NULL; + + // Obtain Device Object + FtdiDevice* device = Nan::ObjectWrap::Unwrap(info.This()); + if(device == NULL) + { + return Nan::ThrowError("No FtdiDevice object found in Java Script object"); + } + + // callback + if(info[0]->IsFunction()) + { + callback = new Nan::Callback(info[0].As()); + } + + Nan::AsyncQueueWorker(new ModemStatusWorker(callback, device, false)); + return; +} + +FT_STATUS FtdiDevice::ModemStatusAsync(FtdiDevice* device, ModemBaton_t* baton) +{ + FT_STATUS ftStatus = FT_GetModemStatus(device->ftHandle, &baton->status); + return ftStatus; +} /***************************** * OPEN Section *****************************/ class OpenWorker : public Nan::AsyncWorker { public: - OpenWorker(Nan::Callback *callback, FtdiDevice* device, Nan::Callback *callback_read) - : Nan::AsyncWorker(callback), device(device), callback_read(callback_read) {} + OpenWorker(Nan::Callback *callback, FtdiDevice* device, Nan::Callback *callback_read, Nan::Callback *callback_modem) + : Nan::AsyncWorker(callback), device(device), callback_read(callback_read), callback_modem(callback_modem) {} ~OpenWorker() {} // Executed inside the worker-thread. @@ -295,7 +430,7 @@ class OpenWorker : public Nan::AsyncWorker { // here, so everything we need for input and output // should go on `this`. void Execute () { - status = FtdiDevice::OpenAsync(device, callback_read); + status = FtdiDevice::OpenAsync(device, callback_read, callback_modem); } // Executed when the async work is complete @@ -317,6 +452,7 @@ class OpenWorker : public Nan::AsyncWorker { uv_mutex_lock(&device->closeMutex); AsyncQueueWorkerPersistent(new ReadWorker(callback_read, device)); + AsyncQueueWorkerPersistent(new ModemStatusWorker(callback_modem, device, true)); } // In case read thread could not be started, dispose the callback else @@ -346,6 +482,7 @@ class OpenWorker : public Nan::AsyncWorker { FT_STATUS status; FtdiDevice* device; Nan::Callback *callback_read; + Nan::Callback *callback_modem; }; NAN_METHOD(FtdiDevice::Open) { @@ -353,6 +490,7 @@ NAN_METHOD(FtdiDevice::Open) { Nan::Callback *callback = NULL; Nan::Callback *callback_read = NULL; + Nan::Callback *callback_modem = NULL; // Get Device Object FtdiDevice* device = Nan::ObjectWrap::Unwrap(info.This()); @@ -361,7 +499,7 @@ NAN_METHOD(FtdiDevice::Open) { return Nan::ThrowError("No FtdiDevice object found in Java Script object"); } - if (info.Length() != 3) + if (info.Length() != 4) { return Nan::ThrowError("open() expects three arguments"); } @@ -374,17 +512,22 @@ NAN_METHOD(FtdiDevice::Open) { // options if(!info[1]->IsFunction()) { - return Nan::ThrowError("open() expects a function (openFisnished) as second argument"); + return Nan::ThrowError("open() expects a function (readFisnished) as second argument"); } Local readCallback = info[1]; - // options if(!info[2]->IsFunction()) { - return Nan::ThrowError("open() expects a function (readFinsihed) as third argument"); + return Nan::ThrowError("open() expects a function (openFinsihed) as third argument"); } Local openCallback = info[2]; + if(!info[3]->IsFunction()) + { + return Nan::ThrowError("open() expects a function (modemChange) as second argument"); + } + Local modemCallback = info[3]; + // Check if device is not already open or opening if(device->deviceState == DeviceState_Open) @@ -402,15 +545,16 @@ NAN_METHOD(FtdiDevice::Open) { device->ExtractDeviceSettings(info[0]->ToObject()); callback_read = new Nan::Callback(readCallback.As()); + callback_modem = new Nan::Callback(modemCallback.As()); callback = new Nan::Callback(openCallback.As()); - Nan::AsyncQueueWorker(new OpenWorker(callback, device, callback_read)); + Nan::AsyncQueueWorker(new OpenWorker(callback, device, callback_read, callback_modem)); } return; } -FT_STATUS FtdiDevice::OpenAsync(FtdiDevice* device, Nan::Callback *callback_read) +FT_STATUS FtdiDevice::OpenAsync(FtdiDevice* device, Nan::Callback *callback_read, Nan::Callback *callback_modem) { FT_STATUS ftStatus; @@ -848,6 +992,14 @@ UCHAR GetParity(const char* string) return FT_PARITY_NONE; } +/** + * Return a boolean true if the bit flag exists in the DWORD + */ +bool checkFlag(DWORD val, int mask) +{ + return (val & mask) != 0; +} + /** * Generates a new C String. It allocates memory for the new * string. Be sure to free the memory as soon as you dont need @@ -951,9 +1103,10 @@ void FtdiDevice::Initialize(v8::Handle target) tpl->SetClassName(Nan::New(JS_CLASS_NAME).ToLocalChecked()); tpl->InstanceTemplate()->SetInternalFieldCount(1); // Prototype - tpl->PrototypeTemplate()->Set(Nan::New(JS_WRITE_FUNCTION).ToLocalChecked(), Nan::New(Write)); - tpl->PrototypeTemplate()->Set(Nan::New(JS_OPEN_FUNCTION).ToLocalChecked(), Nan::New(Open)); - tpl->PrototypeTemplate()->Set(Nan::New(JS_CLOSE_FUNCTION).ToLocalChecked(), Nan::New(Close)); + tpl->PrototypeTemplate()->Set(Nan::New(JS_WRITE_FUNCTION).ToLocalChecked(), Nan::New(Write)->GetFunction()); + tpl->PrototypeTemplate()->Set(Nan::New(JS_OPEN_FUNCTION).ToLocalChecked(), Nan::New(Open)->GetFunction()); + tpl->PrototypeTemplate()->Set(Nan::New(JS_MODEM_STATUS_FUNCTION).ToLocalChecked(), Nan::New(ModemStatus)->GetFunction()); + tpl->PrototypeTemplate()->Set(Nan::New(JS_CLOSE_FUNCTION).ToLocalChecked(), Nan::New(Close)->GetFunction()); Local constructor = tpl->GetFunction(); target->Set(Nan::New(JS_CLASS_NAME).ToLocalChecked(), constructor); diff --git a/src/ftdi_device.h b/src/ftdi_device.h index bcd4fb9..1e9a440 100644 --- a/src/ftdi_device.h +++ b/src/ftdi_device.h @@ -26,6 +26,11 @@ typedef struct DWORD length; } WriteBaton_t; +typedef struct +{ + DWORD status; +} ModemBaton_t; + typedef enum { ConnectType_ByIndex, @@ -78,11 +83,13 @@ class FtdiDevice : public Nan::ObjectWrap static NAN_METHOD(New); static NAN_METHOD(Open); static NAN_METHOD(Write); + static NAN_METHOD(ModemStatus); static NAN_METHOD(Close); - static FT_STATUS OpenAsync(FtdiDevice* device, Nan::Callback *callback_read); + static FT_STATUS OpenAsync(FtdiDevice* device, Nan::Callback *callback_read, Nan::Callback *callback_modem); static FT_STATUS ReadDataAsync(FtdiDevice* device, ReadBaton_t* baton); static FT_STATUS WriteAsync(FtdiDevice* device, WriteBaton_t* baton); + static FT_STATUS ModemStatusAsync(FtdiDevice* device, ModemBaton_t* baton); static FT_STATUS CloseAsync(FtdiDevice* device); void ExtractDeviceSettings(Local options);