├── .npmignore ├── .gitignore ├── README.md ├── hwtest ├── package.json ├── test2.js └── test1.js ├── bin ├── serialportList.js └── serialportTerminal.js ├── arduinoTest ├── arduinoEcho │ └── arduinoEcho.ino ├── stress.js └── requiresComPort.js ├── .jshintrc ├── Gruntfile.js ├── AUTHORS ├── examples ├── readdata.js ├── break.js ├── reset.js ├── drain.js └── logger.js ├── Makefile ├── src ├── serialport_poller.h ├── win │ ├── stdafx.h │ ├── AutoHandle.h │ ├── AutoHModule.h │ ├── AutoHeapAlloc.h │ ├── enumser.h │ └── disphelper.h ├── serialport_poller.cpp ├── serialport.h ├── serialport_win.cpp ├── serialport.cpp └── serialport_unix.cpp ├── test ├── serialport-c.js ├── parsers.js └── serialport-basic.js ├── LICENSE ├── sandbox └── disconnect.js ├── binding.gyp ├── parsers.js ├── package.json ├── appveyor.yml ├── .travis.yml ├── publish-binaries.md ├── test_mocks └── linux-hardware.js ├── changelog.md └── serialport.js /.npmignore: -------------------------------------------------------------------------------- 1 | serialport_native/build 2 | serialport_native.node 3 | serialport_native/.lock-wscript 4 | *.node 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .lock-wscript 2 | *.node 3 | .DS_Store 4 | build/ 5 | node_modules 6 | npm-debug.log 7 | Makefile.gyp 8 | *.Makefile 9 | *.target.gyp.mk 10 | out 11 | gyp-mac-tool 12 | .node_pre_gyprc 13 | /.idea 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Temporary fork of [node-serialport](https://github.com/voodootikigod/node-serialport) to support [Electron](https://github.com/atom/electron). 2 | 3 | Based on [serialport-electron](https://github.com/usefulthink/node-serialport) by Martin Schuhfuss. 4 | 5 | This repository will be deprecated ASAP. 6 | -------------------------------------------------------------------------------- /hwtest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-sptest", 3 | "version": "0.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "author": "", 9 | "license": "ISC", 10 | "dependencies": { 11 | "serialport": "^1.4.6" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /bin/serialportList.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var serialport = require('../'); 4 | var sf = require('sf'); 5 | 6 | serialport.list(function (err, results) { 7 | if (err) { 8 | throw err; 9 | } 10 | 11 | for (var i = 0; i < results.length; i++) { 12 | var item = results[i]; 13 | console.log(sf('{comName,-15} {pnpId,-20} {manufacturer}', item)); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /arduinoTest/arduinoEcho/arduinoEcho.ino: -------------------------------------------------------------------------------- 1 | const int ledPin = 11; 2 | 3 | void setup() { 4 | pinMode(ledPin, OUTPUT); 5 | Serial.begin(9600); 6 | Serial.write("READY"); 7 | } 8 | 9 | void loop() { 10 | 11 | while (Serial.available()) { 12 | digitalWrite(ledPin, HIGH); 13 | Serial.write(Serial.read()); 14 | } 15 | 16 | delay(100); 17 | digitalWrite(ledPin, LOW); 18 | } 19 | -------------------------------------------------------------------------------- /hwtest/test2.js: -------------------------------------------------------------------------------- 1 | var SerialPort = require('serialport').SerialPort; 2 | 3 | process.stdin.on('data', function() {}); 4 | 5 | process.stdin.on('end', function() { 6 | var sp = new SerialPort('/dev/tty.usbmodemfd121'); 7 | sp.on('open', function() { 8 | console.log('SerialPort FD:', sp.fd); 9 | 10 | sp.write('this will not get sent'); 11 | 12 | setTimeout(process.exit, 100); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "strict": true, 4 | "mocha": true, 5 | 6 | "curly": true, 7 | "eqeqeq": true, 8 | "expr": true, 9 | "immed": true, 10 | "latedef": true, 11 | "loopfunc": false, 12 | "noarg": true, 13 | "shadow": false, 14 | "supernew": false, 15 | "undef": true, 16 | 17 | "newcap": true, 18 | "noempty": true, 19 | "nonew": true, 20 | "indent": 2, 21 | "white": true, 22 | "quotmark": true, 23 | 24 | "maxparams": 5, 25 | "maxdepth": 3, 26 | "maxstatements": 60, //default 25 27 | "maxcomplexity": 40 //default 6 28 | } 29 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | 5 | grunt.initConfig({ 6 | mochaTest: { 7 | test: { 8 | options: { reporter: 'spec' }, 9 | src: ['test/**/*.js'] 10 | } 11 | }, 12 | jshint: { 13 | all: ['*.js', 'test/**/*.js', 'arduinoTest/**/*.js'], 14 | options:{ 15 | jshintrc: true 16 | } 17 | } 18 | }); 19 | 20 | grunt.loadNpmTasks('grunt-mocha-test'); 21 | grunt.loadNpmTasks('grunt-contrib-jshint'); 22 | grunt.registerTask('default', ['jshint', 'mochaTest']); 23 | 24 | }; 25 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # The main authors of this library 2 | 3 | Chris Williams 4 | Jay Beavers 5 | Francis Gulotta 6 | Rob Giseburt 7 | 8 | # Other contributors ordered by first contribution. 9 | 10 | Joe Ferner 11 | Esa-Matti Suuronen 12 | Nathan Rajlich 13 | Rick Waldron 14 | 15 | Georges-Etienne Legendre 16 | Duane Johnson 17 | Rod Vagg <@rvagg> 18 | Edgar Silva <@edgarsilva> -------------------------------------------------------------------------------- /examples/readdata.js: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////// 2 | // Use the cool library // 3 | // git://github.com/voodootikigod/node-serialport.git // 4 | // to read the serial port where arduino is sitting. // 5 | //////////////////////////////////////////////////////// 6 | var com = require("serialport"); 7 | 8 | var serialPort = new com.SerialPort("/dev/cu.usbmodemfd121", { 9 | baudrate: 9600, 10 | parser: com.parsers.readline('\r\n') 11 | }); 12 | 13 | serialPort.on('open',function() { 14 | console.log('Port open'); 15 | }); 16 | 17 | serialPort.on('data', function(data) { 18 | console.log(data); 19 | }); 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION := $(shell node -e "console.log(require('./package.json').version)") 2 | 3 | .PHONY: default release 4 | 5 | # Add a default task so we don't release just because someone ran 'make' 6 | default: 7 | @echo "Did you mean to release a new version?" 8 | @echo "If so, run 'make release'." 9 | 10 | release: 11 | @echo "Tagging release $(VERSION)" 12 | @git tag -m "$(VERSION)" v$(VERSION) 13 | 14 | @echo "Pushing tags to GitHub" 15 | @git push --tags 16 | 17 | @echo "Switching to osx-binaries branch" 18 | @git checkout osx-binaries 19 | 20 | @echo "Merging master into osx-binaries" 21 | @git merge --no-ff --commit -m "Merge master into osx-binaries [publish binary]" master 22 | 23 | @echo "Pushing osx-binaries" 24 | @git push 25 | 26 | @echo "Switching to master branch" 27 | @git checkout master 28 | 29 | @echo "Publishing to NPM" 30 | @npm publish ./ 31 | -------------------------------------------------------------------------------- /src/serialport_poller.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Robert Giseburt 2 | // serialport_poller.h Written as a part of https://github.com/voodootikigod/node-serialport 3 | // License to use this is the same as that of node-serialport. 4 | 5 | #ifndef SERIALPORT_POLLER_H 6 | #define SERIALPORT_POLLER_H 7 | 8 | #include 9 | #include "serialport.h" 10 | 11 | class SerialportPoller : public Nan::ObjectWrap { 12 | public: 13 | static void Init(v8::Handle target); 14 | 15 | void callCallback(int status); 16 | 17 | void _start(); 18 | void _stop(); 19 | 20 | private: 21 | SerialportPoller(); 22 | ~SerialportPoller(); 23 | 24 | static NAN_METHOD(New); 25 | static NAN_METHOD(Close); 26 | static NAN_METHOD(Start); 27 | 28 | uv_poll_t poll_handle_; 29 | int fd_; 30 | char errorString[ERROR_STRING_SIZE]; 31 | 32 | Nan::Callback* callback_; 33 | }; 34 | 35 | #endif -------------------------------------------------------------------------------- /test/serialport-c.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var sinon = require('sinon'); 4 | var chai = require('chai'); 5 | var expect = chai.expect; 6 | 7 | describe('SerialPort', function () { 8 | var sandbox; 9 | 10 | beforeEach(function () { 11 | sandbox = sinon.sandbox.create(); 12 | }); 13 | 14 | afterEach(function () { 15 | sandbox.restore(); 16 | }); 17 | 18 | 19 | describe('Initialization', function () { 20 | if (process.version.indexOf('v0.10.') !==0) { 21 | it('does not currently work due to an issue with node unstable release, works in master.', function (done) { 22 | done(); 23 | }); 24 | } else { 25 | it('Throws an error in callback when trying to open an invalid port', function (done) { 26 | var SerialPort = require('../').SerialPort; 27 | var port = new SerialPort('/dev/nullbad', function (err) { 28 | chai.assert.isDefined(err, 'didn\'t get an error'); 29 | done(); 30 | }); 31 | }); 32 | } 33 | }); 34 | 35 | }); 36 | -------------------------------------------------------------------------------- /hwtest/test1.js: -------------------------------------------------------------------------------- 1 | var serialport = require("serialport"); 2 | var SerialPort = serialport.SerialPort; // localize object constructor 3 | 4 | serialport.list(function(err, ports) { 5 | console.log(ports); 6 | 7 | }); 8 | 9 | var sp = new SerialPort("/dev/tty.usbmodem1421", { 10 | baudrate: 9600, 11 | parser: serialport.parsers.readline("\n") 12 | }); 13 | 14 | var i = 1; 15 | 16 | sp.on("open", function() { 17 | console.log('open'); 18 | sp.on('data', function(data) { 19 | console.log('count : ' + (i++)); 20 | console.log('data received: ' + data); 21 | }); 22 | 23 | sp.on('disconnect', function(err) { 24 | console.log('on.disconnect'); 25 | }); 26 | 27 | sp.on('error', function(err) { 28 | console.log("Erreur ?? " + err); 29 | }); 30 | 31 | sp.on('close', function() { 32 | console.log("Someone close the port ..."); 33 | }); 34 | }); 35 | 36 | setTimeout(function() { 37 | sp.close(function() { 38 | console.log("We're closing"); 39 | }); 40 | }, 10000); 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2010, 2011, 2012 Christopher Williams. All rights reserved. 2 | Permission is hereby granted, free of charge, to any person obtaining a copy 3 | of this software and associated documentation files (the "Software"), to 4 | deal in the Software without restriction, including without limitation the 5 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 6 | sell copies of the Software, and to permit persons to whom the Software is 7 | furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in 10 | all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 17 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 18 | IN THE SOFTWARE. -------------------------------------------------------------------------------- /examples/break.js: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////// 2 | // Use the cool library // 3 | // git://github.com/voodootikigod/node-serialport.git // 4 | // to send break condition. // 5 | //////////////////////////////////////////////////////// 6 | var com = require("serialport"); 7 | 8 | var serialPort = new com.SerialPort("/dev/tty.usbserial-A700eTSd", { 9 | baudrate: 115200, 10 | parser: com.parsers.readline('\r\n') 11 | }, false); 12 | 13 | serialPort.on('open',function() { 14 | console.log('Port open'); 15 | }); 16 | 17 | serialPort.on('data', function(data) { 18 | console.log(data); 19 | }); 20 | 21 | function asserting() { 22 | console.log('asserting'); 23 | serialPort.set({brk:true}, function(err, something) { 24 | console.log('asserted'); 25 | setTimeout(clear, 5000); 26 | }); 27 | } 28 | 29 | function clear() { 30 | console.log('clearing'); 31 | serialPort.set({brk:false}, function(err, something) { 32 | console.log('clear'); 33 | setTimeout(done, 5000); 34 | }); 35 | } 36 | 37 | function done() { 38 | console.log("done breaking"); 39 | } 40 | -------------------------------------------------------------------------------- /sandbox/disconnect.js: -------------------------------------------------------------------------------- 1 | // disconnect.js -- an attempt track down the disconnected issue. 2 | 3 | //"use strict"; 4 | var serialPort = require("../serialport"); 5 | 6 | var sp; 7 | function activate () { 8 | 9 | if (sp) { 10 | sp = new serialPort.SerialPort(process.env.SERIAL_PORT, { 11 | baudRate: 115200, 12 | parser : serialPort.parsers.readline(';') 13 | }); 14 | 15 | 16 | sp.on('data', function (data) { 17 | console.log('on.data', data); 18 | sp.close(function(err) { 19 | console.log('close()', err); 20 | }); 21 | }); 22 | 23 | sp.on('close', function (err) { 24 | console.log('on.close'); 25 | sp = null; 26 | }); 27 | 28 | 29 | sp.on('disconnect', function (err) { 30 | console.log('on.disconnect'); 31 | 32 | 33 | }); 34 | 35 | 36 | sp.on('error', function (err) { 37 | console.error("on.error", err); 38 | }); 39 | 40 | sp.on('open', function () { 41 | console.log('on.open'); 42 | sp.write("foobar;", function(err, chars) { 43 | console.log('write()', chars, err); 44 | }); 45 | }); 46 | 47 | } 48 | } 49 | 50 | activate() 51 | 52 | //keep alive 53 | setInterval(function() { activate(); }, 10000); -------------------------------------------------------------------------------- /src/win/stdafx.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define CENUMERATESERIAL_USE_STL //Uncomment this line if you want to test the STL support in CEnumerateSerial 4 | 5 | #ifndef _SECURE_ATL 6 | #define _SECURE_ATL 1 //Use the Secure C Runtime in ATL 7 | #endif 8 | 9 | #ifndef VC_EXTRALEAN 10 | #define VC_EXTRALEAN 11 | #endif 12 | 13 | #ifndef WINVER 14 | #define WINVER 0x0500 15 | #endif 16 | 17 | #ifndef _WIN32_WINNT 18 | #define _WIN32_WINNT 0x0500 19 | #endif 20 | 21 | #ifndef _WIN32_WINDOWS 22 | #define _WIN32_WINDOWS 0x0500 23 | #endif 24 | 25 | #ifndef _WIN32_IE 26 | #define _WIN32_IE 0x0500 27 | #endif 28 | 29 | #ifndef CENUMERATESERIAL_USE_STL 30 | #define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // some CString constructors will be explicit 31 | 32 | #define _AFX_ALL_WARNINGS // turns off MFC's hiding of some common and often safely ignored warning messages 33 | 34 | #include 35 | #include 36 | #include 37 | #else 38 | #include "stdstring.h" 39 | #define NO_ENUMSERIAL_USING_WMI 40 | #endif 41 | 42 | #include 43 | 44 | #define NO_ENUMSERIAL_USING_ENUMPORTS 45 | #define NO_ENUMSERIAL_USING_SETUPAPI1 46 | #define NO_ENUMSERIAL_USING_SETUPAPI2 47 | #define NO_ENUMSERIAL_USING_WMI 48 | #define NO_ENUMSERIAL_USING_COMDB 49 | #define NO_ENUMSERIAL_USING_CREATEFILE 50 | #define NO_ENUMSERIAL_USING_GETDEFAULTCOMMCONFIG 51 | #define NO_ENUMSERIAL_USING_REGISTRY -------------------------------------------------------------------------------- /examples/reset.js: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////// 2 | // Use the cool library // 3 | // git://github.com/voodootikigod/node-serialport.git // 4 | // to reset an arduino. // 5 | //////////////////////////////////////////////////////// 6 | var com = require("serialport"); 7 | 8 | var serialPort = new com.SerialPort("/dev/tty.usbmodem1411", { 9 | baudrate: 115200, 10 | parser: com.parsers.readline('\r\n') 11 | }); 12 | 13 | serialPort.on('open',function() { 14 | console.log('Port open'); 15 | // note you get a dtr for free on port open so lets wait 5 seconds 16 | // to make sure ours is seperate from that one 17 | setTimeout(asserting, 5000); 18 | }); 19 | 20 | serialPort.on('data', function(data) { 21 | console.log(data); 22 | }); 23 | 24 | function asserting() { 25 | console.log('asserting'); 26 | //NOTE: you actually de-assert rts and dtr to toggle! 27 | serialPort.set({rts:false, dtr:false}, function(err, something) { 28 | console.log('asserted'); 29 | setTimeout(clear, 5000); 30 | }); 31 | } 32 | 33 | function clear() { 34 | console.log('clearing'); 35 | serialPort.set({rts:true, dtr:true}, function(err, something) { 36 | console.log('clear'); 37 | setTimeout(done, 5000); 38 | }); 39 | } 40 | 41 | function done() { 42 | console.log("done resetting"); 43 | } 44 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'targets': [ 3 | { 4 | 'target_name': 'serialport', 5 | 'sources': [ 6 | 'src/serialport.cpp', 7 | ], 8 | 'include_dirs': [ 9 | '>>>>', data); 10 | }); 11 | 12 | var message = new Buffer('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'); 13 | 14 | function writeThenDrainThenWait(duration) { 15 | console.log('Calling write...'); 16 | sp.write(message, function() { 17 | console.log('...Write callback returned...'); 18 | // At this point, data may still be buffered and not sent out from the port yet (write function returns asynchrounously). 19 | console.log('...Calling drain...'); 20 | sp.drain(function() { 21 | // Now data has "left the pipe" (tcdrain[1]/FlushFileBuffers[2] finished blocking). 22 | // [1] http://linux.die.net/man/3/tcdrain 23 | // [2] http://msdn.microsoft.com/en-us/library/windows/desktop/aa364439(v=vs.85).aspx 24 | console.log('...Drain callback returned...'); 25 | console.log('...Waiting', duration, 'milliseconds...'); 26 | setInterval(writeThenDrainThenWait, duration); 27 | }); 28 | }); 29 | }; 30 | 31 | function writeThenWait(duration) { 32 | console.log('Calling write...'); 33 | sp.write(message, function() { 34 | console.log('...Write callback returned...'); // Write function call immediately returned (asynchrounous). 35 | console.log('...Waiting', duration, 'milliseconds...'); 36 | // Even though write returned, the data may still be in the pipe, and hasn't reached your robot yet. 37 | setInterval(writeThenWait, duration); 38 | }); 39 | }; 40 | 41 | // Stuff starts happening here 42 | writeThenDrainThenWait(1000); 43 | //writeThenWait(1000); 44 | 45 | }); 46 | -------------------------------------------------------------------------------- /bin/serialportTerminal.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var SerialPort = require('../').SerialPort; 4 | var optimist = require('optimist'); 5 | 6 | var args = optimist 7 | .alias('h', 'help') 8 | .alias('h', '?') 9 | .options('portname', { 10 | alias: 'p', 11 | describe: 'Name of serial port. See serialPortList.js for open serial ports.' 12 | }) 13 | .options('baud', { 14 | describe: 'Baud rate.', 15 | default: 9600 16 | }) 17 | .options('databits', { 18 | describe: 'Data bits.', 19 | default: 8 20 | }) 21 | .options('parity', { 22 | describe: 'Parity.', 23 | default: 'none' 24 | }) 25 | .options('stopbits', { 26 | describe: 'Stop bits.', 27 | default: 1 28 | }) 29 | .options('localecho', { 30 | describe: 'Enable local echo.', 31 | boolean: true 32 | }) 33 | .argv; 34 | 35 | if (args.help) { 36 | optimist.showHelp(); 37 | return process.exit(-1); 38 | } 39 | 40 | if (!args.portname) { 41 | console.error("Serial port name is required."); 42 | return process.exit(-1); 43 | } 44 | 45 | process.stdin.resume(); 46 | process.stdin.setRawMode(true); 47 | process.stdin.on('data', function (s) { 48 | if (s[0] === 0x03) { 49 | port.close(); 50 | process.exit(0); 51 | } 52 | if (args.localecho) { 53 | if (s[0] === 0x0d) { 54 | process.stdout.write('\n'); 55 | } else { 56 | process.stdout.write(s); 57 | } 58 | } 59 | port.write(s, function (err) { 60 | if (err) { 61 | console.log(err); 62 | } 63 | }); 64 | }); 65 | 66 | var openOptions = { 67 | baudRate: args.baud, 68 | dataBits: args.databits, 69 | parity: args.parity, 70 | stopBits: args.stopbits 71 | }; 72 | var port = new SerialPort(args.portname, openOptions); 73 | 74 | port.on('data', function (data) { 75 | process.stdout.write(data.toString()); 76 | }); 77 | 78 | port.on('error', function (err) { 79 | console.log(err); 80 | }); 81 | -------------------------------------------------------------------------------- /examples/logger.js: -------------------------------------------------------------------------------- 1 | /* 2 | Simple example that takes a command line provided serial port destination and routes the output to a file of the same name with .log appended to the port name. 3 | 4 | usage: node logger.js /dev/tty.usbserial 5 | 6 | */ 7 | 8 | var SerialPort = require("serialport"); 9 | var fs = require("fs"); 10 | var port = process.argv[2]; 11 | var baudrate = process.argv[3]; 12 | var active = false; 13 | 14 | function attemptLogging(fd, port, baudrate) { 15 | if (!active) { 16 | fs.stat(port, function (err, stats) { 17 | if (!err) { 18 | active = true; 19 | 20 | var serialPort = new SerialPort.SerialPort(port, { 21 | baudrate: baudrate 22 | }); 23 | fs.write(fd, "\n------------------------------------------------------------\nOpening SerialPort: "+target+" at "+Date.now()+"\n------------------------------------------------------------\n"); 24 | serialPort.on("data", function (data) { 25 | fs.write(fd, data.toString()); 26 | }); 27 | serialPort.on("close", function (data) { 28 | active = false; 29 | fs.write(fd, "\n------------------------------------------------------------\nClosing SerialPort: "+target+" at "+Date.now()+"\n------------------------------------------------------------\n"); 30 | }); 31 | } 32 | }); 33 | } 34 | } 35 | 36 | if (!port) { 37 | console.log("You must specify a serial port location."); 38 | } else { 39 | var target = port.split("/"); 40 | target = target[target.length-1]+".log"; 41 | if (!baudrate) { 42 | baudrate = 115200; 43 | } 44 | fs.open("./"+target, 'w', function (err, fd) { 45 | setInterval(function () { 46 | if (!active) { 47 | try { 48 | attemptLogging(fd, port, baudrate); 49 | } catch (e) { 50 | // Error means port is not available for listening. 51 | active = false; 52 | } 53 | } 54 | }, 1000); 55 | }); 56 | } 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/win/AutoHModule.h: -------------------------------------------------------------------------------- 1 | /* 2 | Module : AutoHModule.h 3 | Purpose: Defines the interface for a class which supports auto closing of a HMODULE via FreeLibrary and 4 | setting of the last Win32 error via SetLastError 5 | Created: PJN / 10-01-2013 6 | 7 | Copyright (c) 2013 by PJ Naughter (Web: www.naughter.com, Email: pjna@naughter.com) 8 | 9 | All rights reserved. 10 | 11 | Copyright / Usage Details: 12 | 13 | You are allowed to include the source code in any product (commercial, shareware, freeware or otherwise) 14 | when your product is released in binary form. You are allowed to modify the source code in any way you want 15 | except you cannot modify the copyright details at the top of each module. If you want to distribute source 16 | code with your application, then you are only allowed to distribute versions released by the author. This is 17 | to maintain a single distribution point for the source code. 18 | 19 | */ 20 | 21 | 22 | ///////////////////////// Macros / Structs etc //////////////////////////////// 23 | 24 | #pragma once 25 | 26 | #ifndef __AUTOHMODULE_H__ 27 | #define __AUTOHMODULE_H__ 28 | 29 | 30 | ///////////////////////// Classes ///////////////////////////////////////////// 31 | 32 | class CAutoHModule 33 | { 34 | public: 35 | //Constructors / Destructors 36 | CAutoHModule() : m_hModule(NULL), 37 | m_dwError(ERROR_SUCCESS) 38 | { 39 | } 40 | 41 | explicit CAutoHModule(HMODULE hModule) : m_hModule(hModule), 42 | m_dwError(GetLastError()) 43 | { 44 | } 45 | 46 | explicit CAutoHModule(HMODULE hModule, DWORD dwError) : m_hModule(hModule), 47 | m_dwError(dwError) 48 | { 49 | } 50 | 51 | ~CAutoHModule() 52 | { 53 | if (m_hModule != NULL) 54 | { 55 | FreeLibrary(m_hModule); 56 | m_hModule = NULL; 57 | } 58 | SetLastError(m_dwError); 59 | } 60 | 61 | operator HMODULE() 62 | { 63 | return m_hModule; 64 | } 65 | 66 | //Member variables 67 | HMODULE m_hModule; 68 | DWORD m_dwError; 69 | }; 70 | 71 | 72 | #endif //__AUTOHMODULE_H__ 73 | -------------------------------------------------------------------------------- /arduinoTest/stress.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | /*global describe, it */ 3 | 'use strict'; 4 | 5 | var chai = require('chai'); 6 | var util = require('util'); 7 | var serialPort = require('../serialport'); 8 | var colors = require('colors'); 9 | var fs = require('fs'); 10 | var memwatch = require('memwatch'); 11 | 12 | describe ('stress', function() { 13 | 14 | describe('long running', function() { 15 | it('opens a port and sends data, repeated indefinitely', function (done) { 16 | 17 | var hd = new memwatch.HeapDiff(); 18 | 19 | memwatch.on('leak', function(info) { 20 | fs.appendFile('leak.log', util.inspect(info)); 21 | console.log(util.inspect(info).red); 22 | 23 | var diff = hd.end(); 24 | fs.appendFile('heapdiff.log', util.inspect(diff)); 25 | hd = new memwatch.HeapDiff(); 26 | }); 27 | 28 | memwatch.on('stats', function (stats) { 29 | fs.appendFile('stats.log', util.inspect(stats)); 30 | console.log(util.inspect(stats).green); 31 | }); 32 | 33 | serialPort.list(function(err, ports) { 34 | 35 | chai.assert.isUndefined(err, util.inspect(err)); 36 | chai.assert.isDefined(ports, 'ports is not defined'); 37 | chai.assert.isTrue(ports.length > 0, 'no ports found'); 38 | 39 | var data = new Buffer('hello'); 40 | 41 | var port = new serialPort.SerialPort(ports.slice(-1)[0].comName, null, false); 42 | port.on('error', function(err) { 43 | chai.assert.fail(util.inspect(err)); 44 | }); 45 | 46 | port.on('data', function (data) { 47 | }); 48 | 49 | port.open(function(err) { 50 | chai.assert.isUndefined(err, util.inspect(err)); 51 | 52 | 53 | var intervalId = setInterval(function () { 54 | port.write(data); 55 | }, 20 ); 56 | 57 | setTimeout(function() { 58 | 59 | clearInterval(intervalId); 60 | 61 | var diff = hd.end(); 62 | fs.appendFile('heapdiff.log', util.inspect(diff)); 63 | 64 | console.log(util.inspect(diff).green); 65 | 66 | done(); 67 | 68 | }, 3590000); 69 | }); 70 | }); 71 | }); 72 | }); 73 | 74 | }); 75 | -------------------------------------------------------------------------------- /parsers.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | // Copyright 2011 Chris Williams 5 | 6 | module.exports = { 7 | raw: function (emitter, buffer) { 8 | emitter.emit('data', buffer); 9 | }, 10 | 11 | //encoding: ascii utf8 utf16le ucs2 base64 binary hex 12 | //More: http://nodejs.org/api/buffer.html#buffer_buffer 13 | readline: function (delimiter, encoding) { 14 | if (typeof delimiter === 'undefined' || delimiter === null) { delimiter = '\r'; } 15 | if (typeof encoding === 'undefined' || encoding === null) { encoding = 'utf8'; } 16 | // Delimiter buffer saved in closure 17 | var data = ''; 18 | return function (emitter, buffer) { 19 | // Collect data 20 | data += buffer.toString(encoding); 21 | // Split collected data by delimiter 22 | var parts = data.split(delimiter); 23 | data = parts.pop(); 24 | parts.forEach(function (part) { 25 | emitter.emit('data', part); 26 | }); 27 | }; 28 | }, 29 | 30 | // Emit a data event every `length` bytes 31 | byteLength: function(length) { 32 | var data = new Buffer(0); 33 | return function(emitter, buffer){ 34 | data = Buffer.concat([data, buffer]); 35 | while (data.length >= length) { 36 | var out = data.slice(0,length); 37 | data = data.slice(length); 38 | emitter.emit('data', out); 39 | } 40 | }; 41 | }, 42 | 43 | //Emit a data event each time a byte sequence (delimiter is an array of byte) is found 44 | //Sample usage : byteDelimiter([10, 13]) 45 | byteDelimiter: function (delimiter) { 46 | if( Object.prototype.toString.call( delimiter ) !== '[object Array]' ){ 47 | delimiter = [ delimiter ]; 48 | } 49 | var buf = []; 50 | var nextDelimIndex = 0; 51 | return function (emitter, buffer) { 52 | for (var i = 0; i < buffer.length; i++) { 53 | buf[buf.length] = buffer[i]; 54 | if(buf[buf.length-1] === delimiter[nextDelimIndex]) { 55 | nextDelimIndex++; 56 | } 57 | if(nextDelimIndex === delimiter.length) { 58 | emitter.emit('data', buf); 59 | buf = []; 60 | nextDelimIndex = 0; 61 | } 62 | } 63 | }; 64 | } 65 | }; 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serialport-electron", 3 | "version": "2.0.1-electron-0.30.8", 4 | "description": "Temporary fork to support Electron (to be deprecated)", 5 | "author": { 6 | "name": "Chris Williams", 7 | "email": "voodootikigod@gmail.com", 8 | "url": "http://www.voodootikigod.com" 9 | }, 10 | "binary": { 11 | "module_name": "serialport", 12 | "module_path": "./build/{module_name}/v{version}/{configuration}/{node_abi}-{platform}-{arch}/", 13 | "remote_path": "./{module_name}/v{version}/{configuration}/", 14 | "package_name": "{node_abi}-{platform}-{arch}.tar.gz", 15 | "host": "https://node-serialport.s3.amazonaws.com" 16 | }, 17 | "main": "./serialport", 18 | "repository": { 19 | "type": "git", 20 | "url": "git@github.com:ddm/node-serialport.git" 21 | }, 22 | "maintainers": [{ 23 | "name": "Jacob Rosenthal", 24 | "email": "jakerosenthal@gmail.com" 25 | }, { 26 | "name": "Chris Williams", 27 | "email": "voodootikigod@gmail.com" 28 | }, { 29 | "name": "Joe Ferner", 30 | "email": "joe.ferner@nearinfinity.com" 31 | }, { 32 | "name": "Jay Beavers", 33 | "email": "jay@hikinghomeschoolers.org" 34 | }, { 35 | "name": "Rob Giseburt", 36 | "email": "giseburt@gmail.com" 37 | }, { 38 | "name": "Francis Gulotta", 39 | "email": "wizard@roborooter.com" 40 | }], 41 | "dependencies": { 42 | "async": "0.9.0", 43 | "bindings": "1.2.1", 44 | "debug": "^2.1.1", 45 | "nan": "~2.0.0", 46 | "node-gyp": "^2.0.2", 47 | "optimist": "~0.6.1", 48 | "sf": "0.1.7" 49 | }, 50 | "devDependencies": { 51 | "aws-sdk": "*", 52 | "mocha": "*", 53 | "chai": "*", 54 | "sinon-chai": "*", 55 | "sinon": "*", 56 | "grunt": "*", 57 | "grunt-cli": "*", 58 | "grunt-mocha-test": "*", 59 | "grunt-contrib-jshint": "*", 60 | "sandboxed-module": "~0.3.0" 61 | }, 62 | "engines": { 63 | "node": ">= 0.10.0" 64 | }, 65 | "bin": { 66 | "serialportlist": "./bin/serialportList.js", 67 | "serialportterm": "./bin/serialportTerminal.js" 68 | }, 69 | "license": "MIT", 70 | "scripts": { 71 | "install": "node-gyp rebuild --target=0.30.8 --dist-url=https://atom.io/download/atom-shell", 72 | "test": "grunt --verbose" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/win/AutoHeapAlloc.h: -------------------------------------------------------------------------------- 1 | /* 2 | Module : AutoHeapAlloc.h 3 | Purpose: Defines the interface for a class which supports auto closing of a heap pointer allocated via HeapAlloc 4 | Created: PJN / 10-01-2013 5 | 6 | Copyright (c) 2013 by PJ Naughter (Web: www.naughter.com, Email: pjna@naughter.com) 7 | 8 | All rights reserved. 9 | 10 | Copyright / Usage Details: 11 | 12 | You are allowed to include the source code in any product (commercial, shareware, freeware or otherwise) 13 | when your product is released in binary form. You are allowed to modify the source code in any way you want 14 | except you cannot modify the copyright details at the top of each module. If you want to distribute source 15 | code with your application, then you are only allowed to distribute versions released by the author. This is 16 | to maintain a single distribution point for the source code. 17 | 18 | */ 19 | 20 | 21 | ///////////////////////// Macros / Structs etc //////////////////////////////// 22 | 23 | #pragma once 24 | 25 | #ifndef __AUTOHEAPALLOC_H__ 26 | #define __AUTOHEAPALLOC_H__ 27 | 28 | 29 | ///////////////////////// Includes //////////////////////////////////////////// 30 | 31 | #include 32 | 33 | 34 | ///////////////////////// Classes ///////////////////////////////////////////// 35 | 36 | class CAutoHeapAlloc 37 | { 38 | public: 39 | //Constructors / Destructors 40 | CAutoHeapAlloc(HANDLE hHeap = GetProcessHeap(), DWORD dwHeapFreeFlags = 0) : m_pData(NULL), 41 | m_hHeap(hHeap), 42 | m_dwHeapFreeFlags(dwHeapFreeFlags) 43 | { 44 | } 45 | 46 | BOOL Allocate(SIZE_T dwBytes, DWORD dwFlags = 0) 47 | { 48 | //Validate our parameters 49 | assert(m_pData == NULL); 50 | 51 | m_pData = HeapAlloc(m_hHeap, dwFlags, dwBytes); 52 | return (m_pData != NULL); 53 | } 54 | 55 | ~CAutoHeapAlloc() 56 | { 57 | if (m_pData != NULL) 58 | { 59 | HeapFree(m_hHeap, m_dwHeapFreeFlags, m_pData); 60 | m_pData = NULL; 61 | } 62 | } 63 | 64 | //Methods 65 | 66 | //Member variables 67 | LPVOID m_pData; 68 | HANDLE m_hHeap; 69 | DWORD m_dwHeapFreeFlags; 70 | }; 71 | 72 | #endif //__AUTOHEAPALLOC_H__ 73 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # environment variables 2 | environment: 3 | node_pre_gyp_secretAccessKey: 4 | secure: wJRlPhmqDgwxllTxhhG5JJosBf3nKYxcXO6NfGnrUUnxmECHeNTsBrldqW8+EjyK 5 | node_pre_gyp_accessKeyId: 6 | secure: cgGBaLa6xiPo5duak1/xU5hxPCn1aUQqkQQZf27QQp0= 7 | 8 | matrix: 9 | - nodejs_version: "0.10" 10 | - nodejs_version: "0.11" 11 | - nodejs_version: "0.12" 12 | 13 | 14 | # to add several platforms to build matrix: 15 | platform: 16 | - x64 17 | 18 | install: 19 | - cmd: ECHO "INSTALL:" 20 | - cmd: ECHO "APPVEYOR_REPO_COMMIT_MESSAGE ->" 21 | - cmd: ECHO %APPVEYOR_REPO_COMMIT_MESSAGE% 22 | - cmd: SET COMMIT_MSG="%APPVEYOR_REPO_COMMIT_MESSAGE%" 23 | - cmd: SET NODIST_PREFIX=C:\nodist 24 | - cmd: SET PATH=C:\nodist\bin;%PATH% 25 | - cmd: SET PUBLISH_BINARY=false 26 | # Check to verify the branch is the same than latest tag, if so 27 | # then we publish the binaries if everything else is successful. 28 | - cmd: git describe --tags --always HEAD > _git_tag.tmp 29 | - cmd: SET /p GIT_TAG=<_git_tag.tmp 30 | - cmd: ECHO "LATEST LOCAL TAG:" 31 | - cmd: ECHO %GIT_TAG% 32 | - cmd: ECHO "APPVEYOR REPO BRANCH/TAG:" 33 | - cmd: ECHO %APPVEYOR_REPO_BRANCH% 34 | - cmd: DEL _git_tag.tmp 35 | - cmd: IF x%APPVEYOR_REPO_BRANCH%==x%GIT_TAG% SET PUBLISH_BINARY=true 36 | # Or look for commit message containing `[publish binary]` 37 | - cmd: IF not x%COMMIT_MSG:[publish binary]=%==x%COMMIT_MSG% SET PUBLISH_BINARY=true 38 | - cmd: ECHO "Env Var PUBLISH_BINARY:" 39 | - cmd: ECHO %PUBLISH_BINARY% 40 | - cmd: git clone https://github.com/marcelklehr/nodist.git c:\nodist 2>&1 41 | - cmd: SET NODIST_X64=0 42 | - cmd: nodist update 43 | - cmd: nodist %nodejs_version% 44 | - cmd: npm install -g node-gyp 45 | 46 | # to run your custom scripts instead of automatic MSBuild 47 | build_script: 48 | - cmd: ECHO "BUILDING ia32 version:" 49 | - cmd: npm install --build-from-source 50 | - cmd: SET PATH=%cd%\node_modules\.bin\;%PATH% 51 | - cmd: node serialport.js 52 | - cmd: ECHO "PUBLISH ia32 package:" 53 | - cmd: npm install aws-sdk 54 | - cmd: IF %PUBLISH_BINARY%==true (node-pre-gyp package publish 2>&1) 55 | - cmd: node-pre-gyp clean 56 | - cmd: node-gyp clean 57 | - cmd: rmdir /q /s node_modules 58 | - cmd: ECHO "INSTALLING x64 node.js:" 59 | # We add newest MSBuild so we can use the correct libs for the x64 version 60 | - cmd: SET PATH=C:\Program Files (x86)\MSBuild\12.0\bin\;%PATH% 61 | - cmd: SET PATH=C:\python27;%PATH% 62 | - cmd: set NODIST_X64=1 63 | - cmd: nodist update 64 | - cmd: nodist %nodejs_version% 65 | - cmd: npm install -g node-gyp 66 | - cmd: ECHO "BUILDING x64 version:" 67 | - cmd: npm install --build-from-source --msvs_version=2013 68 | - cmd: ECHO "PUBLISH package:" 69 | - cmd: npm install aws-sdk 70 | - cmd: IF %PUBLISH_BINARY%==true (node-pre-gyp package publish 2>&1) 71 | - cmd: node-pre-gyp clean 72 | - cmd: node-gyp clean 73 | 74 | on_success: 75 | # test installing from binary package works 76 | - cmd: ECHO "ON SUCCESS:" 77 | - cmd: ECHO "Try installing from binary:" 78 | - cmd: IF %PUBLISH_BINARY%==true npm install --fallback-to-build=false 79 | # Clean Directories. 80 | - cmd: node-pre-gyp clean 81 | # Print Available Binaries 82 | - cmd: node-pre-gyp info 83 | 84 | test: OFF 85 | 86 | deploy: OFF 87 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | - osx 4 | # turn on new container-based infrastructure 5 | sudo: false 6 | language: cpp 7 | addons: 8 | apt: 9 | sources: 10 | - ubuntu-toolchain-r-test 11 | packages: 12 | - g++-4.8 13 | - g++-4.8-multilib 14 | - gcc-multilib 15 | env: 16 | matrix: 17 | - TRAVIS_NODE_VERSION="0.10" 18 | - TRAVIS_NODE_VERSION="0.12" 19 | - TRAVIS_NODE_VERSION="4" 20 | - TRAVIS_NODE_VERSION="0.10" ARCH="x86" 21 | - TRAVIS_NODE_VERSION="0.12" ARCH="x86" 22 | - TRAVIS_NODE_VERSION="4" ARCH="x86" 23 | global: 24 | - secure: "Zg7w8hTpgVfm6JeTScsMhaexFcVS5N2oI9O/6MLtsm3CUhb0Txq65y4y/a5DU3re6Pl1ryE21anVLdg4RGDeykCauQtg8nFzuGXBfNn9bRTnsIfkiS9LNkbSZ8h3c7EsyX0NJ7LVgnkPh/XXMqMj4Nh0ovbBCwZJ6k2ZvccvDvU=" 25 | - secure: "VS7JTJYfcCDOiWZH6g+Lm4y+QFuCB++4GnYvWqqaUljebuB2l8gZhDrmQtWKFPBEZP21ptjtpNWJC6hCUf07qJ81vVpb6wMUikfOOLN0nulED+AC5HtCxYnfwIu1i0z00YxaSfvozMXKk9TCLabE0ognoDV+XV1bQoYm8668z7E=" 26 | matrix: 27 | exclude: 28 | - os: osx 29 | env: TRAVIS_NODE_VERSION="0.10" ARCH="x86" 30 | - os: osx 31 | env: TRAVIS_NODE_VERSION="0.12" ARCH="x86" 32 | - os: osx 33 | env: TRAVIS_NODE_VERSION="4" ARCH="x86" 34 | 35 | before_install: 36 | # reinstall latest nvm 37 | - rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && (cd ~/.nvm && git checkout `git describe --abbrev=0 --tags`) && source ~/.nvm/nvm.sh && nvm install $TRAVIS_NODE_VERSION 38 | - BASE_URL=$(node -p "v=parseInt(process.versions.node),(v>=1&&v<4?'https://iojs.org/dist/':'https://nodejs.org/dist/')+process.version") 39 | - X86_FILE=$(node -p "v=parseInt(process.versions.node),(v>=1&&v<4?'iojs':'node')+'-'+process.version+'-'+process.platform+'-x86'") 40 | # download node if testing x86 architecture 41 | - if [[ "$ARCH" == "x86" ]]; then wget $BASE_URL/$X86_FILE.tar.gz; tar -xf $X86_FILE.tar.gz; export PATH=$X86_FILE/bin:$PATH; fi 42 | # reinstall npm and node-pre-gyp 43 | - npm install -g node-gyp node-pre-gyp 44 | # print versions 45 | - npm --version 46 | - node-gyp --version 47 | - node-pre-gyp --version 48 | # use g++-4.8 on Linux 49 | - if [[ $TRAVIS_OS_NAME == "linux" ]]; then export CXX=g++-4.8; fi 50 | - $CXX --version 51 | # get commit message 52 | - COMMIT_MESSAGE=$(git show -s --format=%B $TRAVIS_COMMIT | tr -d '\n') 53 | # figure out if we should publish 54 | - PUBLISH_BINARY=false 55 | # if we are building a tag then publish 56 | - if [[ $TRAVIS_BRANCH == `git describe --tags --always HEAD` ]]; then PUBLISH_BINARY=true; fi; 57 | # or if we put [publish binary] in the commit message 58 | - if test "${COMMIT_MESSAGE#*'[publish binary]'}" != "$COMMIT_MESSAGE"; then PUBLISH_BINARY=true; fi; 59 | # If version is 0.9 do not publish to avoid duplicated binary error, caused by 0.9 and 0.10 60 | # returning same v number in node.js. 61 | - if [[ "$TRAVIS_NODE_VERSION" == '0.9' ]]; then PUBLISH_BINARY=false; fi; 62 | 63 | install: 64 | # ensure source install works 65 | - npm install --build-from-source 66 | 67 | script: 68 | # test our module 69 | - node serialport.js 70 | - npm test 71 | - echo "Publishing native platform Binary Package? ->" $PUBLISH_BINARY 72 | # if publishing, do it 73 | - if [[ $PUBLISH_BINARY == true ]]; then node-pre-gyp package publish; fi; 74 | # cleanup 75 | - node-pre-gyp clean 76 | - node-gyp clean 77 | 78 | after_success: 79 | # if success then query and display all published binaries 80 | - node-pre-gyp info 81 | -------------------------------------------------------------------------------- /publish-binaries.md: -------------------------------------------------------------------------------- 1 | How to publish the pre compiled binaries. 2 | ========================================= 3 | 4 | ## Setup for Linux, Windows and OSX 5 | 6 | Every time a new tag for the latest release is pushed to github the continuous integration 7 | builds in Travis-CI and AppVeyor will generate the binaries for each platform and architecture, 8 | package and publish to the AS3 bucket. 9 | 10 | This can be checked in the .travis.yml file and appveyor.yml file. Within the files there are two 11 | methods for publishing new binaries for each version, one is if a `git tag` is detected; the other 12 | can be triggered by passing the string `[publish binary]` in the commit message itself. 13 | 14 | We also have an automated make task, we should always use this task to avoid forgetting any steps 15 | (like merging into the `osx-binaries` branch). 16 | 17 | The process for generating the binaries, publishing and releasing the npm module should be as follows: 18 | 19 | 1. Merge all changes and new features into master. 20 | 2. Bump up version of npm module in `package.json`. 21 | 3. execute make task: `make release` 22 | 23 | This task will do the following for you: 24 | 25 | 1. Generate new tags based on package.json version number 26 | 2. Push tags to Github 27 | 3. Checkout into `osx-binaries` branch 28 | 4. Merge `master` into `osx-binaries` 29 | 5. Push `osx-binaries` 30 | 6. Checkout master 31 | 7. Finally it will run `npm publish` 32 | 33 | With this we will make sure the binaries for all platforms and architectures will be generated each time 34 | a new version is released. 35 | 36 | 37 | ## Config Travis, AppVeyor and Github to generate all of the binaries. 38 | 39 | Before we are able to run everything stated above some steps need to be taken. 40 | Specifically for being able to publish the pre compiled binaries to AWS-S3. The 41 | correct keys need to be setup in the travis and appveyor `.yml` files. This needs 42 | to be done by the admin of the repo, in the case of Travis, and the owner of the account, 43 | in the case of appveyor. 44 | 45 | ### Setting up secure keys in Travis. 46 | 47 | Setting up the keys in Travis is easy if you have ruby and ruby gems installed and working then install: 48 | 49 | `gem install travis` 50 | 51 | After the travis gem is installed run the following command for each of the required keys: 52 | 53 | `travis encrypt SOMEVAR=secretvalue` 54 | 55 | And substitute the values in the `.travis.yml` file for the new ones. Detailed instructions can 56 | be found here: http://docs.travis-ci.com/user/environment-variables/#Secure-Variables 57 | 58 | ### Setting up secure keys in AppVeyor 59 | 60 | It is even easier than Travis, you do not need to install anything, just go to your account and 61 | click in `encrypt tool`, there enter the values in the input field and click encrypt. Same as with 62 | Travis we then need to substitute the newly generated values for the old ones. 63 | 64 | Detailed instructions can be found here: http://www.appveyor.com/docs/build-configuration#secure-variables 65 | 66 | ### OSX binaries 67 | 68 | Since Travis does not support config file for multiple OSs we need to create a new branch that contains 69 | a slightly different version of the .travis.yml file to compile for OSX. The branch needs to be called 70 | `osx-binaries` and be based of `master` once the pre-compiled binaries PR has been merged in. 71 | 72 | If we want to release a new version of the OSX binaries manually we can do it by pushing a new commit to 73 | the osx-binaries branch that contains the `[publish binary]` string, we can push an empty commit as follows: 74 | 75 | ```bash 76 | $ git checkout osx-binaries 77 | $ git commit --allow-empty -m "Publish new version of pre-compiled binaries to AS3 [publish binary]" 78 | $ git push origin osx-binaries 79 | ``` 80 | 81 | The travis script will verify that the commit message contains the string `[publish binary]` and upload the 82 | binary packages. 83 | -------------------------------------------------------------------------------- /test/parsers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var chai = require('chai'); 4 | var expect = chai.expect; 5 | var sinonChai = require('sinon-chai'); 6 | var sinon = require('sinon'); 7 | chai.use(sinonChai); 8 | 9 | var parsers = require('../parsers'); 10 | 11 | describe('parsers', function () { 12 | 13 | describe('#raw', function () { 14 | it('emits data exactly as it\'s written', function () { 15 | var data = new Buffer('BOGUS'); 16 | var spy = sinon.spy(); 17 | parsers.raw({ emit: spy }, data); 18 | expect(spy.getCall(0).args[1]).to.deep.equal(new Buffer('BOGUS')); 19 | }); 20 | }); 21 | 22 | describe('#readline', function () { 23 | it('emits data events split on a delimiter', function () { 24 | var data = new Buffer('I love robots\rEach and Every One\r'); 25 | var spy = sinon.spy(); 26 | var parser = parsers.readline(); 27 | parser({ emit: spy }, data); 28 | expect(spy).to.have.been.calledWith('data', 'I love robots'); 29 | expect(spy).to.have.been.calledWith('data', 'Each and Every One'); 30 | }); 31 | }); 32 | 33 | describe('#byteLength', function(){ 34 | it('emits data events every 8 bytes', function () { 35 | var data = new Buffer('Robots are so freaking cool!'); 36 | var spy = sinon.spy(); 37 | var parser = parsers.byteLength(8); 38 | parser({ emit: spy }, data); 39 | expect(spy.callCount).to.equal(3); 40 | expect(spy.getCall(0).args[1].length).to.equal(8); 41 | expect(spy.getCall(0).args[1]).to.deep.equal(new Buffer('Robots a')); 42 | expect(spy.getCall(1).args[1]).to.deep.equal(new Buffer('re so fr')); 43 | expect(spy.getCall(2).args[1]).to.deep.equal(new Buffer('eaking c')); 44 | }); 45 | }); 46 | 47 | describe('#rawdelimiter', function() { 48 | it('emits data events every time it meets 00x 00x', function() { 49 | var data = new Buffer('This could be\0\0binary data\0\0sent from a Moteino\0\0'); 50 | var parser = parsers.byteDelimiter([0, 0]); 51 | var spy = sinon.spy(); 52 | parser({ emit: spy }, data); 53 | expect(spy.callCount).to.equal(3); 54 | expect(spy.getCall(0).args[1]).to.have.length(15); 55 | expect(spy.getCall(0).args[1]).to.satisfy(function(d) { return d[d.length-1] === 0; }); 56 | expect(spy.getCall(1).args[1]).to.have.length(13); 57 | expect(spy.getCall(1).args[1]).to.satisfy(function(d) { return d[d.length-1] === 0; }); 58 | expect(spy.getCall(2).args[1]).to.have.length(21); 59 | expect(spy.getCall(2).args[1]).to.satisfy(function(d) { return d[d.length-1] === 0; }); 60 | }); 61 | it('accepts single byte delimiter', function() { 62 | var data = new Buffer('This could be\0binary data\0sent from a Moteino\0'); 63 | var parser = parsers.byteDelimiter(0); 64 | var spy = sinon.spy(); 65 | parser({ emit: spy }, data); 66 | expect(spy.callCount).to.equal(3); 67 | }); 68 | it('Works when buffer starts with delimiter', function() { 69 | var data = new Buffer('\0Hello\0World\0'); 70 | var parser = parsers.byteDelimiter(0); 71 | var spy = sinon.spy(); 72 | parser({ emit: spy }, data); 73 | expect(spy.callCount).to.equal(3); 74 | }); 75 | it('continues looking for delimiters in the next buffers', function() { 76 | var data1 = new Buffer('This could be\0\0binary '); 77 | var data2 = new Buffer('data\0\0sent from a Moteino\0\0'); 78 | var parser = parsers.byteDelimiter([0,0]); 79 | var spy = sinon.spy(); 80 | parser({ emit: spy }, data1); 81 | parser({ emit: spy }, data2); 82 | expect(spy.callCount).to.equal(3); 83 | expect(spy.getCall(0).args[1]).to.have.length(15); 84 | expect(spy.getCall(0).args[1]).to.satisfy(function(d) { return d[d.length-1] === 0; }); 85 | expect(spy.getCall(1).args[1]).to.have.length(13); 86 | expect(spy.getCall(1).args[1]).to.satisfy(function(d) { return d[d.length-1] === 0; }); 87 | expect(spy.getCall(2).args[1]).to.have.length(21); 88 | expect(spy.getCall(2).args[1]).to.satisfy(function(d) { return d[d.length-1] === 0; }); 89 | }); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /src/serialport_poller.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Robert Giseburt 2 | // serialport_poller.cpp Written as a part of https://github.com/voodootikigod/node-serialport 3 | // License to use this is the same as that of node-serialport. 4 | 5 | #include 6 | #include "serialport_poller.h" 7 | 8 | using namespace v8; 9 | 10 | static Nan::Persistent serialportpoller_constructor; 11 | 12 | SerialportPoller::SerialportPoller() : Nan::ObjectWrap() {}; 13 | SerialportPoller::~SerialportPoller() { 14 | // printf("~SerialportPoller\n"); 15 | delete callback_; 16 | }; 17 | 18 | void _serialportReadable(uv_poll_t *req, int status, int events) { 19 | SerialportPoller* sp = (SerialportPoller*) req->data; 20 | // We can stop polling until we have read all of the data... 21 | sp->_stop(); 22 | sp->callCallback(status); 23 | } 24 | 25 | void SerialportPoller::callCallback(int status) { 26 | Nan::HandleScope scope; 27 | // uv_work_t* req = new uv_work_t; 28 | 29 | // Call the callback to go read more data... 30 | 31 | v8::Local argv[1]; 32 | if(status != 0) { 33 | // error handling changed in libuv, see: 34 | // https://github.com/joyent/libuv/commit/3ee4d3f183331 35 | #ifdef UV_ERRNO_H_ 36 | const char* err_string = uv_strerror(status); 37 | #else 38 | uv_err_t errno = uv_last_error(uv_default_loop()); 39 | const char* err_string = uv_strerror(errno); 40 | #endif 41 | snprintf(this->errorString, sizeof(this->errorString), "Error %s on polling", err_string); 42 | argv[0] = v8::Exception::Error(Nan::New(this->errorString).ToLocalChecked()); 43 | } else { 44 | argv[0] = Nan::Undefined(); 45 | } 46 | 47 | callback_->Call(1, argv); 48 | } 49 | 50 | 51 | 52 | void SerialportPoller::Init(Handle target) { 53 | Nan::HandleScope scope; 54 | 55 | // Prepare constructor template 56 | Local tpl = Nan::New(New); 57 | tpl->SetClassName(Nan::New("SerialportPoller").ToLocalChecked()); 58 | tpl->InstanceTemplate()->SetInternalFieldCount(1); 59 | 60 | 61 | // Prototype 62 | 63 | // SerialportPoller.close() 64 | Nan::SetPrototypeTemplate(tpl, "close", 65 | Nan::GetFunction(Nan::New(Close)).ToLocalChecked()); 66 | 67 | // SerialportPoller.start() 68 | Nan::SetPrototypeTemplate(tpl, "start", 69 | Nan::GetFunction(Nan::New(Start)).ToLocalChecked()); 70 | 71 | serialportpoller_constructor.Reset(tpl); 72 | 73 | Nan::Set(target, Nan::New("SerialportPoller").ToLocalChecked(), Nan::GetFunction(tpl).ToLocalChecked()); 74 | } 75 | 76 | NAN_METHOD(SerialportPoller::New) { 77 | 78 | if(!info[0]->IsInt32()) { 79 | Nan::ThrowTypeError("First argument must be an fd"); 80 | return; 81 | } 82 | 83 | if(!info[1]->IsFunction()) { 84 | Nan::ThrowTypeError("Third argument must be a function"); 85 | return; 86 | } 87 | 88 | SerialportPoller* obj = new SerialportPoller(); 89 | obj->fd_ = info[0]->ToInt32()->Int32Value(); 90 | obj->callback_ = new Nan::Callback(info[1].As()); 91 | // obj->callCallback(); 92 | 93 | obj->Wrap(info.This()); 94 | 95 | obj->poll_handle_.data = obj; 96 | /*int r = */uv_poll_init(uv_default_loop(), &obj->poll_handle_, obj->fd_); 97 | 98 | uv_poll_start(&obj->poll_handle_, UV_READABLE, _serialportReadable); 99 | 100 | info.GetReturnValue().Set(info.This()); 101 | } 102 | 103 | void SerialportPoller::_start() { 104 | uv_poll_start(&poll_handle_, UV_READABLE, _serialportReadable); 105 | } 106 | 107 | void SerialportPoller::_stop() { 108 | uv_poll_stop(&poll_handle_); 109 | } 110 | 111 | 112 | NAN_METHOD(SerialportPoller::Start) { 113 | 114 | SerialportPoller* obj = Nan::ObjectWrap::Unwrap(info.This()); 115 | obj->_start(); 116 | 117 | return; 118 | } 119 | NAN_METHOD(SerialportPoller::Close) { 120 | 121 | SerialportPoller* obj = Nan::ObjectWrap::Unwrap(info.This()); 122 | obj->_stop(); 123 | 124 | // DO SOMETHING! 125 | 126 | return; 127 | } 128 | -------------------------------------------------------------------------------- /arduinoTest/requiresComPort.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var chai = require('chai'); 4 | var util = require('util'); 5 | var serialPort = require('../serialport'); 6 | 7 | describe ('requiresComPort', function() { 8 | 9 | describe('echo hello', function() { 10 | it('sends hello to the last port and validates that it is received back (see arduinoEcho.ino for echo sketch)', function(done) { 11 | serialPort.list(function(err, ports) { 12 | 13 | chai.assert.isUndefined(err, util.inspect(err)); 14 | chai.assert.isDefined(ports, 'ports is not defined'); 15 | chai.assert.isTrue(ports.length > 0, 'no ports found'); 16 | 17 | var data = new Buffer('hello'); 18 | 19 | var port = new serialPort.SerialPort(ports.slice(-1)[0].comName, null, false); 20 | port.on('error', function(err) { 21 | chai.assert.fail(util.inspect(err)); 22 | }); 23 | 24 | port.on('data', function(d) { 25 | chai.assert.equal(data.toString(), d.toString(), 'incorrect data received'); 26 | port.close(function(err) { 27 | chai.assert.isUndefined(err, util.inspect(err)); 28 | done(); 29 | }); 30 | }); 31 | 32 | port.open(function(err) { 33 | chai.assert.isUndefined(err, util.inspect(err)); 34 | port.write(data); 35 | }); 36 | }); 37 | }); 38 | }); 39 | 40 | describe('relaxed baud rate', function() { 41 | it('opens a port with a non-standard baud rate', function(done) { 42 | serialPort.list(function(err, ports) { 43 | 44 | chai.assert.isUndefined(err, util.inspect(err)); 45 | chai.assert.isDefined(ports, 'ports is not defined'); 46 | chai.assert.isTrue(ports.length > 0, 'no ports found'); 47 | 48 | var port = new serialPort.SerialPort(ports.slice(-1)[0].comName, {baudrate: 5}, false); 49 | port.on('error', function(err) { 50 | chai.assert.fail(util.inspect(err)); 51 | }); 52 | 53 | port.open(function(err) { 54 | chai.assert.isUndefined(err, util.inspect(err)); 55 | port.close(function(err) { 56 | chai.assert.isUndefined(err, util.inspect(err)); 57 | done(); 58 | }); 59 | }); 60 | }); 61 | }); 62 | }); 63 | 64 | describe('simple write', function() { 65 | it('opens a port and sends data without encountering error', function(done) { 66 | serialPort.list(function(err, ports) { 67 | 68 | chai.assert.isUndefined(err, util.inspect(err)); 69 | chai.assert.isDefined(ports, 'ports is not defined'); 70 | chai.assert.isTrue(ports.length > 0, 'no ports found'); 71 | 72 | var data = new Buffer('hello'); 73 | 74 | var port = new serialPort.SerialPort(ports.slice(-1)[0].comName, null, false); 75 | port.on('error', function(err) { 76 | chai.assert.fail(util.inspect(err)); 77 | }); 78 | 79 | port.open(function(err) { 80 | chai.assert.isUndefined(err, util.inspect(err)); 81 | port.write(data); 82 | port.close(function(err) { 83 | chai.assert.isUndefined(err, util.inspect(err)); 84 | done(); 85 | }); 86 | }); 87 | }); 88 | }); 89 | }); 90 | 91 | describe('validate close event', function() { 92 | it('opens a port then closes it using events', function(done) { 93 | serialPort.list(function(err, ports) { 94 | 95 | chai.assert.isUndefined(err, util.inspect(err)); 96 | chai.assert.isDefined(ports, 'ports is not defined'); 97 | chai.assert.isTrue(ports.length > 0, 'no ports found'); 98 | 99 | var port = new serialPort.SerialPort(ports.slice(-1)[0].comName, null, false); 100 | 101 | port.on('error', function(err) { 102 | chai.assert.fail(util.inspect(err)); 103 | }); 104 | 105 | port.on('open', function() { 106 | port.close(); 107 | }); 108 | 109 | port.on('close', function() { 110 | done(); 111 | }); 112 | 113 | port.open(); 114 | }); 115 | }); 116 | }); 117 | 118 | }); 119 | -------------------------------------------------------------------------------- /src/serialport.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _serialport_h_ 3 | #define _serialport_h_ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | enum SerialPortParity { 13 | SERIALPORT_PARITY_NONE = 1, 14 | SERIALPORT_PARITY_MARK = 2, 15 | SERIALPORT_PARITY_EVEN = 3, 16 | SERIALPORT_PARITY_ODD = 4, 17 | SERIALPORT_PARITY_SPACE = 5 18 | }; 19 | 20 | enum SerialPortStopBits { 21 | SERIALPORT_STOPBITS_ONE = 1, 22 | SERIALPORT_STOPBITS_ONE_FIVE = 2, 23 | SERIALPORT_STOPBITS_TWO = 3 24 | }; 25 | 26 | #define ERROR_STRING_SIZE 1024 27 | 28 | NAN_METHOD(List); 29 | void EIO_List(uv_work_t* req); 30 | void EIO_AfterList(uv_work_t* req); 31 | 32 | NAN_METHOD(Open); 33 | void EIO_Open(uv_work_t* req); 34 | void EIO_AfterOpen(uv_work_t* req); 35 | void AfterOpenSuccess(int fd, Nan::Callback* dataCallback, Nan::Callback* disconnectedCallback, Nan::Callback* errorCallback); 36 | 37 | NAN_METHOD(Update); 38 | void EIO_Update(uv_work_t* req); 39 | void EIO_AfterUpdate(uv_work_t* req); 40 | 41 | NAN_METHOD(Write); 42 | void EIO_Write(uv_work_t* req); 43 | void EIO_AfterWrite(uv_work_t* req); 44 | 45 | NAN_METHOD(Close); 46 | void EIO_Close(uv_work_t* req); 47 | void EIO_AfterClose(uv_work_t* req); 48 | 49 | NAN_METHOD(Flush); 50 | void EIO_Flush(uv_work_t* req); 51 | void EIO_AfterFlush(uv_work_t* req); 52 | 53 | NAN_METHOD(Set); 54 | void EIO_Set(uv_work_t* req); 55 | void EIO_AfterSet(uv_work_t* req); 56 | 57 | NAN_METHOD(Drain); 58 | void EIO_Drain(uv_work_t* req); 59 | void EIO_AfterDrain(uv_work_t* req); 60 | 61 | SerialPortParity ToParityEnum(const v8::Local& str); 62 | SerialPortStopBits ToStopBitEnum(double stopBits); 63 | 64 | struct OpenBatonPlatformOptions 65 | { 66 | }; 67 | OpenBatonPlatformOptions* ParsePlatformOptions(const v8::Local& options); 68 | 69 | struct OpenBaton { 70 | public: 71 | char path[1024]; 72 | Nan::Callback* callback; 73 | Nan::Callback* dataCallback; 74 | Nan::Callback* disconnectedCallback; 75 | Nan::Callback* errorCallback; 76 | int fd; 77 | int result; 78 | int baudRate; 79 | int dataBits; 80 | int bufferSize; 81 | bool rtscts; 82 | bool xon; 83 | bool xoff; 84 | bool xany; 85 | bool dsrdtr; 86 | bool hupcl; 87 | SerialPortParity parity; 88 | SerialPortStopBits stopBits; 89 | OpenBatonPlatformOptions* platformOptions; 90 | char errorString[ERROR_STRING_SIZE]; 91 | }; 92 | 93 | struct WriteBaton { 94 | public: 95 | int fd; 96 | char* bufferData; 97 | size_t bufferLength; 98 | size_t offset; 99 | Nan::Persistent buffer; 100 | Nan::Callback* callback; 101 | int result; 102 | char errorString[ERROR_STRING_SIZE]; 103 | }; 104 | 105 | struct QueuedWrite { 106 | public: 107 | uv_work_t req; 108 | QueuedWrite *prev; 109 | QueuedWrite *next; 110 | WriteBaton* baton; 111 | 112 | QueuedWrite() { 113 | prev = this; 114 | next = this; 115 | 116 | baton = 0; 117 | }; 118 | 119 | ~QueuedWrite() { 120 | remove(); 121 | }; 122 | 123 | void remove() { 124 | prev->next = next; 125 | next->prev = prev; 126 | 127 | next = this; 128 | prev = this; 129 | }; 130 | 131 | void insert_tail(QueuedWrite *qw) { 132 | qw->next = this; 133 | qw->prev = this->prev; 134 | qw->prev->next = qw; 135 | this->prev = qw; 136 | }; 137 | 138 | bool empty() { 139 | return next == this; 140 | }; 141 | 142 | }; 143 | 144 | struct CloseBaton { 145 | public: 146 | int fd; 147 | Nan::Callback* callback; 148 | char errorString[ERROR_STRING_SIZE]; 149 | }; 150 | 151 | struct ListResultItem { 152 | public: 153 | std::string comName; 154 | std::string manufacturer; 155 | std::string serialNumber; 156 | std::string pnpId; 157 | std::string locationId; 158 | std::string vendorId; 159 | std::string productId; 160 | }; 161 | 162 | struct ListBaton { 163 | public: 164 | Nan::Callback* callback; 165 | std::list results; 166 | char errorString[ERROR_STRING_SIZE]; 167 | }; 168 | 169 | struct FlushBaton { 170 | public: 171 | int fd; 172 | Nan::Callback* callback; 173 | int result; 174 | char errorString[ERROR_STRING_SIZE]; 175 | }; 176 | 177 | struct SetBaton { 178 | public: 179 | int fd; 180 | Nan::Callback* callback; 181 | int result; 182 | char errorString[ERROR_STRING_SIZE]; 183 | bool rts; 184 | bool cts; 185 | bool dtr; 186 | bool dsr; 187 | bool brk; 188 | 189 | }; 190 | 191 | struct DrainBaton { 192 | public: 193 | int fd; 194 | Nan::Callback* callback; 195 | int result; 196 | char errorString[ERROR_STRING_SIZE]; 197 | }; 198 | 199 | int setup(int fd, OpenBaton *data); 200 | 201 | #endif 202 | -------------------------------------------------------------------------------- /test_mocks/linux-hardware.js: -------------------------------------------------------------------------------- 1 | // This takes a serial-port factory and mocks the shit out of it in complete isolation per require of this file 2 | 3 | "use strict"; 4 | 5 | var mockSerialportPoller = function (hardware) { 6 | var Poller = function (path, cb) { 7 | this.port = hardware.ports[path]; 8 | if (!this.port) { 9 | throw new Error(path + " does not exist - please call hardware.createPort(path) first"); 10 | } 11 | this.port.poller = this; 12 | this.polling = null; 13 | this.cb = cb; 14 | }; 15 | Poller.prototype.start = function () { 16 | this.polling = true; 17 | }; 18 | Poller.prototype.close = function () { 19 | this.polling = false; 20 | }; 21 | Poller.prototype.detectRead = function () { 22 | this.cb(); 23 | }; 24 | return Poller; 25 | }; 26 | 27 | var Hardware = function () { 28 | this.ports = {}; 29 | this.mockBinding = { 30 | list: this.list.bind(this), 31 | open: this.open.bind(this), 32 | write: this.write.bind(this), 33 | close: this.close.bind(this), 34 | flush: this.flush.bind(this), 35 | SerialportPoller: mockSerialportPoller(this) 36 | }; 37 | }; 38 | 39 | Hardware.prototype.reset = function () { 40 | this.ports = {}; 41 | }; 42 | 43 | Hardware.prototype.createPort = function (path) { 44 | this.ports[path] = { 45 | data: new Buffer(0), 46 | lastWrite: null, 47 | open: false, 48 | poller: null, 49 | info: { 50 | comName: path, 51 | manufacturer: '', 52 | serialNumber: '', 53 | pnpId: '', 54 | locationId: '', 55 | vendorId: '', 56 | productId: '' 57 | } 58 | }; 59 | }; 60 | 61 | Hardware.prototype.emitData = function (path, data) { 62 | var port = this.ports[path]; 63 | if(!port) { 64 | throw new Error(path + " does not exist - please call hardware.createPort(path) first"); 65 | } 66 | port.data = Buffer.concat([port.data, data]); 67 | port.poller && port.poller.detectRead(); 68 | }; 69 | 70 | Hardware.prototype.disconnect = function (path) { 71 | var port = this.ports[path]; 72 | if (!port) { 73 | throw new Error(path + " does not exist - please call hardware.createPort(path) first"); 74 | } 75 | port.openOpt.disconnectedCallback(); 76 | }; 77 | 78 | Hardware.prototype.list = function (cb) { 79 | var ports = this.ports; 80 | var info = Object.keys(ports).map(function (path) { 81 | return ports[path].info; 82 | }); 83 | cb && cb(info); 84 | }; 85 | 86 | Hardware.prototype.open = function (path, opt, cb) { 87 | var port = this.ports[path]; 88 | if (!port) { 89 | return cb(new Error(path + " does not exist - please call hardware.createPort(path) first")); 90 | } 91 | port.open = true; 92 | port.openOpt = opt; 93 | cb && cb(null, path); // using path as the fd for convience 94 | }; 95 | 96 | Hardware.prototype.write = function (path, buffer, cb) { 97 | var port = this.ports[path]; 98 | if (!port) { 99 | return cb(new Error(path + " does not exist - please call hardware.createPort(path) first")); 100 | } 101 | port.lastWrite = new Buffer(buffer); //copy 102 | cb && cb(null, buffer.length); 103 | }; 104 | 105 | Hardware.prototype.close = function (path, cb) { 106 | var port = this.ports[path]; 107 | if (!port) { 108 | return cb(new Error(path + " does not exist - please call hardware.createPort(path) first")); 109 | } 110 | port.open = false; 111 | cb && cb(null); 112 | }; 113 | 114 | Hardware.prototype.flush = function (path, cb) { 115 | var port = this.ports[path]; 116 | if (!port) { 117 | return cb(new Error(path + " does not exist - please call hardware.createPort(path) first")); 118 | } 119 | cb && cb(null, undefined); 120 | }; 121 | 122 | Hardware.prototype.fakeRead = function (path, buffer, offset, length, position, cb) { 123 | var port = this.ports[path]; 124 | if (!port) { 125 | return cb(new Error(path + " does not exist - please call hardware.createPort(path) first")); 126 | } 127 | if (port.data.length === 0) { 128 | return cb(null, 0, buffer); 129 | } 130 | if ((offset + length) > buffer.length) { 131 | throw new Error("Length extends beyond buffer"); 132 | } 133 | 134 | // node v0.8 doesn't like a slice that is bigger then available data 135 | length = port.data.length < length ? port.data.length : length; 136 | var read = port.data.slice(0, length); 137 | port.data = port.data.slice(length); 138 | read.copy(buffer, offset); 139 | cb(null, read.length, buffer); 140 | }; 141 | 142 | var hardware = new Hardware(); 143 | 144 | var SandboxedModule = require('sandboxed-module'); 145 | 146 | var serialPort = SandboxedModule.require('../serialport', { 147 | requires: { 148 | fs: { 149 | read: hardware.fakeRead.bind(hardware) 150 | } 151 | }, 152 | globals: { 153 | process: { 154 | platform: 'darwin', 155 | nextTick: process.nextTick 156 | } 157 | } 158 | }); 159 | 160 | serialPort.hardware = hardware; 161 | serialPort.SerialPortBinding = hardware.mockBinding; 162 | 163 | module.exports = serialPort; 164 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | Version 1.7.1 2 | ------------- 3 | - Fixed breaking issues in underlying code. (@voodootikigod) 4 | 5 | Version 1.7.0 6 | ------------- 7 | - Fix for #518 and #498 If you pass to SerialPort function (constructor) the same object for argument "options", inside SerialPort will use it as internal object and adds handlers to it. That causes only one callback to work on different SerialPort instances. (@bullmastiffo) 8 | - Update README.md #515 (@arahlf) 9 | - Fix a memory leak in SerialportPoller::New (@jpilet) 10 | - unix support for update baudrate #502 (@jacobrosenthal) 11 | - set cloexec after open, possible fix for #468 (@jacobrosenthal) 12 | - Added hang up on close option to constructor. #495 (@jbendes) 13 | - Upgraded NAN to 1.8.4 due to complaints from io.js 2.x users. (@imyller) 14 | 15 | Version 1.6.1 16 | ------------- 17 | - Upgraded to NAN 1.7.0 18 | - #476 adding break signal 19 | 20 | Version 1.6.0 21 | ------------- 22 | - Long standing issue resolved thanks to @jacobrosenthal for adding control signals into the serialport. YAY! 23 | - Fix for #426 24 | - Ability to return from inside loop #453 25 | - Emits for close/disconnect. #452 26 | 27 | Version 1.5.0 28 | ------------- 29 | - Fixed to work with io.js and node 0.11.x by upgrading to recent nan 1.6.2 30 | 31 | 32 | Version 1.4.8 33 | ------------- 34 | - Simple bump for the binary. 35 | 36 | Version 1.4.7 37 | ------------- 38 | - Fix for Issue #398 - Dropped sent characters on OSX and Linux 39 | - Fix for Issue #387 - added isOpen 40 | - removed a residual comment 41 | - Added osx control signalling 42 | - Fix for Issue #401 43 | - Fix for double write callbacks. 44 | - detect a serialport disconnect on linux. 45 | 46 | Version 1.4.6 47 | ------------- 48 | - Emit error on serialport when explicit handler present. Fixes gh-369 49 | - Fix for windows and Node 0.11.13 (atom-shell) 50 | - Fix for broken Travis-CI build. 51 | 52 | Version 1.4.5 53 | ------------- 54 | - Identified and report issue to node.js core about recent 0.11.x system. 55 | - Removed support for 0.8.x 56 | - Updated dependencies 57 | 58 | Version 1.4.4 59 | ------------- 60 | - Fix for delete error. 61 | 62 | Version 1.3.0 63 | ------------- 64 | - Merged NAN integration for Node 0.8->0.11+ compatibility (#270) 65 | 66 | Version 1.2.5 67 | ------------- 68 | - Fixed an issue with pool handlers being global instead of instance isolation (Issue #252 and #255 thanks: foobarth !!! ) 69 | 70 | 71 | Version 1.2.4 72 | ------------- 73 | - Resolved parity error under linux as reported here: https://github.com/voodootikigod/node-serialport/issues/219 74 | 75 | 76 | Version 1.1.3 77 | ------------- 78 | - Remove ATL dependency on Windows (added Visual Studio Pro requirement) 79 | - Update build instructions 80 | - Four small bugfixes 81 | 82 | Version 1.0.7 83 | ------------- 84 | - Guaranteed in-order delivery of messages thanks to Jay Beavers and bnoordhuis 85 | 86 | Version 1.0.6 87 | ------------- 88 | - Support higher baud rates in Mac OS X 89 | 90 | Version 1.0.5 91 | ------------- 92 | - Added flush support. 93 | 94 | Version 1.0.4 95 | ------------- 96 | - Fix for arduino firmata support on windows thanks to @jgautier. 97 | 98 | Version 1.0.3 99 | ------------- 100 | - Fixed issue 65 - https://github.com/voodootikigod/node-serialport/issues/65 101 | - Added note in readme about what is required for the system to be able to compile module, should solve 90% of issues. 102 | 103 | Version 1.0.2 104 | ------------- 105 | - Fixed issue 59 - https://github.com/voodootikigod/node-serialport/issues/59 106 | 107 | Version 1.0.1 108 | ------------- 109 | - Fixed items from Firmata 110 | - Added flexibility for options (camelcase or all lower) 111 | 112 | Version 1.0.0 113 | ------------- 114 | - Added Windows support thanks to Joe Ferner. 115 | - Merged in the various underlying changes from node-serialport2 complete thanks to Joe Ferner for that! 116 | - Verified against known installations. 117 | 118 | 119 | Version 0.6.5 120 | ------------- 121 | - Added SetBaudRate, SetDTR; Custom Baud Rates 122 | - New "close" listener when device being disconnected 123 | 124 | Version 0.2.8 125 | ------------- 126 | - BufferSize fix for readstream (thanks jgautier, you rock) 127 | 128 | Version 0.2.7 129 | ------------- 130 | - Make no port available be an exception not error emitted - Ticket #12. 131 | 132 | Version 0.2.5 - Version 0.2.6 133 | ----------------------------- 134 | - Debugging issue with IOWatcher not holding in the event loop in node.js. 135 | - Converted to ReadStream instead of IOWatcher. 136 | 137 | Version 0.2.4 138 | ------------- 139 | - Integrated arduino tests (rwaldron) 140 | - Integrated options bug fix (w1nk) 141 | - Integrated hardware flow control for crazier serial port action (w1nk) 142 | 143 | Version 0.2.3 144 | ------------- 145 | - Something amazing that has since been lost and forgotten. 146 | 147 | Version 0.2.2 148 | ------------- 149 | - Integrated enhanced version of arduino/readline that actually buffers the data (epeli) 150 | 151 | Version 0.2.1 152 | ------------- 153 | - Refactored the parsing code upon data receipt, now allows for dynamic specification of how incoming data is handled. 154 | - Revised creation interface to use named parameters as an object versions positional parameters. 155 | 156 | Version: 0.2 157 | ------------ 158 | - Upgraded to node v. 0.4.X compatibility 159 | 160 | All other version are not recorded. -------------------------------------------------------------------------------- /src/win/enumser.h: -------------------------------------------------------------------------------- 1 | /* 2 | Module : enumser.h 3 | Purpose: Defines the interface for a class to enumerate the serial ports installed on a PC 4 | using a number of different approaches 5 | Created: PJN / 03-11-1998 6 | 7 | Copyright (c) 1998 - 2013 by PJ Naughter (Web: www.naughter.com, Email: pjna@naughter.com) 8 | 9 | All rights reserved. 10 | 11 | Copyright / Usage Details: 12 | 13 | You are allowed to include the source code in any product (commercial, shareware, freeware or otherwise) 14 | when your product is released in binary form. You are allowed to modify the source code in any way you want 15 | except you cannot modify the copyright details at the top of each module. If you want to distribute source 16 | code with your application, then you are only allowed to distribute versions released by the author. This is 17 | to maintain a single distribution point for the source code. 18 | 19 | */ 20 | 21 | 22 | ///////////////////////// Macros / Structs etc //////////////////////////////// 23 | 24 | #pragma once 25 | 26 | #ifndef __ENUMSER_H__ 27 | #define __ENUMSER_H__ 28 | 29 | #ifndef CENUMERATESERIAL_EXT_CLASS 30 | #define CENUMERATESERIAL_EXT_CLASS 31 | #endif 32 | 33 | 34 | ///////////////////////// Includes //////////////////////////////////////////// 35 | 36 | #if defined CENUMERATESERIAL_USE_STL 37 | #ifndef _VECTOR_ 38 | #include 39 | #pragma message("To avoid this message, please put vector in your pre compiled header (normally stdafx.h)") 40 | #endif 41 | #ifndef _STRING_ 42 | #include 43 | #pragma message("To avoid this message, please put string in your pre compiled header (normally stdafx.h)") 44 | #endif 45 | #else 46 | #if defined _AFX 47 | #ifndef __AFXTEMPL_H__ 48 | #include 49 | #pragma message("To avoid this message, please put afxtempl.h in your pre compiled header (normally stdafx.h)") 50 | #endif 51 | #else 52 | #ifndef __ATLSTR_H__ 53 | #include 54 | #pragma message("To avoid this message, please put atlstr.h in your pre compiled header (normally stdafx.h)") 55 | #endif 56 | #endif 57 | #endif 58 | 59 | 60 | ///////////////////////// Classes ///////////////////////////////////////////// 61 | 62 | class CENUMERATESERIAL_EXT_CLASS CEnumerateSerial 63 | { 64 | public: 65 | //Methods 66 | #ifndef NO_ENUMSERIAL_USING_CREATEFILE 67 | #if defined CENUMERATESERIAL_USE_STL 68 | static BOOL UsingCreateFile(std::vector& ports); 69 | #elif defined _AFX 70 | static BOOL UsingCreateFile(CUIntArray& ports); 71 | #else 72 | static BOOL UsingCreateFile(CSimpleArray& ports); 73 | #endif 74 | #endif 75 | 76 | #ifndef NO_ENUMSERIAL_USING_QUERYDOSDEVICE 77 | #if defined CENUMERATESERIAL_USE_STL 78 | static BOOL UsingQueryDosDevice(std::vector& ports); 79 | #elif defined _AFX 80 | static BOOL UsingQueryDosDevice(CUIntArray& ports); 81 | #else 82 | static BOOL UsingQueryDosDevice(CSimpleArray& ports); 83 | #endif 84 | #endif 85 | 86 | #ifndef NO_ENUMSERIAL_USING_GETDEFAULTCOMMCONFIG 87 | #if defined CENUMERATESERIAL_USE_STL 88 | static BOOL UsingGetDefaultCommConfig(std::vector& ports); 89 | #elif defined _AFX 90 | static BOOL UsingGetDefaultCommConfig(CUIntArray& ports); 91 | #else 92 | static BOOL UsingGetDefaultCommConfig(CSimpleArray& ports); 93 | #endif 94 | #endif 95 | 96 | #ifndef NO_ENUMSERIAL_USING_SETUPAPI1 97 | #if defined CENUMERATESERIAL_USE_STL 98 | #if defined _UNICODE 99 | static BOOL UsingSetupAPI1(std::vector& ports, std::vector& friendlyNames); 100 | #else 101 | static BOOL UsingSetupAPI1(std::vector& ports, std::vector& friendlyNames); 102 | #endif 103 | #elif defined _AFX 104 | static BOOL UsingSetupAPI1(CUIntArray& ports, CStringArray& friendlyNames); 105 | #else 106 | static BOOL UsingSetupAPI1(CSimpleArray& ports, CSimpleArray& friendlyNames); 107 | #endif 108 | #endif 109 | 110 | #ifndef NO_ENUMSERIAL_USING_SETUPAPI2 111 | #if defined CENUMERATESERIAL_USE_STL 112 | #if defined _UNICODE 113 | static BOOL UsingSetupAPI2(std::vector& ports, std::vector& friendlyNames); 114 | #else 115 | static BOOL UsingSetupAPI2(std::vector& ports, std::vector& friendlyNames); 116 | #endif 117 | #elif defined _AFX 118 | static BOOL UsingSetupAPI2(CUIntArray& ports, CStringArray& friendlyNames); 119 | #else 120 | static BOOL UsingSetupAPI2(CSimpleArray& ports, CSimpleArray& friendlyNames); 121 | #endif 122 | #endif 123 | 124 | #ifndef NO_ENUMSERIAL_USING_ENUMPORTS 125 | #if defined CENUMERATESERIAL_USE_STL 126 | static BOOL UsingEnumPorts(std::vector& ports); 127 | #elif defined _AFX 128 | static BOOL UsingEnumPorts(CUIntArray& ports); 129 | #else 130 | static BOOL UsingEnumPorts(CSimpleArray& ports); 131 | #endif 132 | #endif 133 | 134 | #ifndef NO_ENUMSERIAL_USING_WMI 135 | #if defined CENUMERATESERIAL_USE_STL 136 | #if defined _UNICODE 137 | static BOOL UsingWMI(std::vector& ports, std::vector& friendlyNames); 138 | #else 139 | static BOOL UsingWMI(std::vector& ports, std::vector& friendlyNames); 140 | #endif 141 | #elif defined _AFX 142 | static BOOL UsingWMI(CUIntArray& ports, CStringArray& friendlyNames); 143 | #else 144 | static BOOL UsingWMI(CSimpleArray& ports, CSimpleArray& friendlyNames); 145 | #endif 146 | #endif 147 | 148 | #ifndef NO_ENUMSERIAL_USING_COMDB 149 | #if defined CENUMERATESERIAL_USE_STL 150 | static BOOL UsingComDB(std::vector& ports); 151 | #elif defined _AFX 152 | static BOOL UsingComDB(CUIntArray& ports); 153 | #else 154 | static BOOL UsingComDB(CSimpleArray& ports); 155 | #endif 156 | #endif 157 | 158 | #ifndef NO_ENUMSERIAL_USING_REGISTRY 159 | #if defined CENUMERATESERIAL_USE_STL 160 | #if defined _UNICODE 161 | static BOOL UsingRegistry(std::vector& ports); 162 | #else 163 | static BOOL UsingRegistry(std::vector& ports); 164 | #endif 165 | #elif defined _AFX 166 | static BOOL UsingRegistry(CStringArray& ports); 167 | #else 168 | static BOOL UsingRegistry(CSimpleArray& ports); 169 | #endif 170 | #endif 171 | 172 | protected: 173 | //Methods 174 | #if !defined(NO_ENUMSERIAL_USING_SETUPAPI1) || !defined(NO_ENUMSERIAL_USING_SETUPAPI2) 175 | static BOOL RegQueryValueString(HKEY kKey, LPCTSTR lpValueName, LPTSTR& pszValue); 176 | static BOOL QueryRegistryPortName(HKEY hDeviceKey, int& nPort); 177 | #endif 178 | #if !defined(NO_ENUMSERIAL_USING_SETUPAPI1) || !defined(NO_ENUMSERIAL_USING_SETUPAPI2) || !defined(NO_ENUMSERIAL_USING_COMDB) 179 | static HMODULE LoadLibraryFromSystem32(LPCTSTR lpFileName); 180 | #endif 181 | static BOOL IsNumeric(LPCSTR pszString, BOOL bIgnoreColon); 182 | static BOOL IsNumeric(LPCWSTR pszString, BOOL bIgnoreColon); 183 | }; 184 | 185 | #endif //__ENUMSER_H__ 186 | -------------------------------------------------------------------------------- /test/serialport-basic.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var sinon = require('sinon'); 4 | var chai = require('chai'); 5 | var expect = chai.expect; 6 | 7 | var MockedSerialPort = require('../test_mocks/linux-hardware'); 8 | var SerialPort = MockedSerialPort.SerialPort; 9 | var hardware = MockedSerialPort.hardware; 10 | 11 | describe('SerialPort', function () { 12 | var sandbox; 13 | 14 | beforeEach(function () { 15 | sandbox = sinon.sandbox.create(); 16 | 17 | // Create a port for fun and profit 18 | hardware.reset(); 19 | hardware.createPort('/dev/exists'); 20 | }); 21 | 22 | afterEach(function () { 23 | sandbox.restore(); 24 | }); 25 | 26 | describe('Constructor', function () { 27 | it('opens the port immediately', function (done) { 28 | var port = new SerialPort('/dev/exists', function (err) { 29 | expect(err).to.not.be.ok; 30 | done(); 31 | }); 32 | }); 33 | 34 | it('emits the open event', function (done) { 35 | var port = new SerialPort('/dev/exists'); 36 | port.on('open', function(){ 37 | done(); 38 | }); 39 | }); 40 | 41 | it('emits an error on the factory when erroring without a callback', function (done) { 42 | // finish the test on error 43 | MockedSerialPort.once('error', function (err) { 44 | chai.assert.isDefined(err, 'didn\'t get an error'); 45 | done(); 46 | }); 47 | 48 | var port = new SerialPort('/dev/johnJacobJingleheimerSchmidt'); 49 | }); 50 | 51 | it('emits an error on the serialport when explicit error handler present', function (done) { 52 | var port = new SerialPort('/dev/johnJacobJingleheimerSchmidt'); 53 | 54 | port.once('error', function(err) { 55 | chai.assert.isDefined(err); 56 | done(); 57 | }); 58 | }); 59 | 60 | it('errors with invalid databits', function (done) { 61 | var errorCallback = function (err) { 62 | chai.assert.isDefined(err, 'err is not defined'); 63 | done(); 64 | }; 65 | 66 | var port = new SerialPort('/dev/exists', { databits : 19 }, false, errorCallback); 67 | }); 68 | 69 | it('errors with invalid stopbits', function (done) { 70 | var errorCallback = function (err) { 71 | chai.assert.isDefined(err, 'err is not defined'); 72 | done(); 73 | }; 74 | 75 | var port = new SerialPort('/dev/exists', { stopbits : 19 }, false, errorCallback); 76 | }); 77 | 78 | it('errors with invalid parity', function (done) { 79 | var errorCallback = function (err) { 80 | chai.assert.isDefined(err, 'err is not defined'); 81 | done(); 82 | }; 83 | 84 | var port = new SerialPort('/dev/exists', { parity : 'pumpkins' }, false, errorCallback); 85 | }); 86 | 87 | it('errors with invalid flow control', function (done) { 88 | var errorCallback = function (err) { 89 | chai.assert.isDefined(err, 'err is not defined'); 90 | done(); 91 | }; 92 | 93 | var port = new SerialPort('/dev/exists', { flowcontrol : ['pumpkins'] }, false, errorCallback); 94 | }); 95 | 96 | 97 | it('allows optional options', function (done) { 98 | var cb = function () {}; 99 | var port = new SerialPort('/dev/exists', cb); 100 | expect(typeof port.options).to.eq('object'); 101 | done(); 102 | }); 103 | 104 | }); 105 | 106 | describe('Functions', function () { 107 | 108 | it('write errors when serialport not open', function (done) { 109 | var cb = function () {}; 110 | var port = new SerialPort('/dev/exists', false, cb); 111 | port.write(null, function(err){ 112 | chai.assert.isDefined(err, 'err is not defined'); 113 | done(); 114 | }); 115 | }); 116 | 117 | it('close errors when serialport not open', function (done) { 118 | var cb = function () {}; 119 | var port = new SerialPort('/dev/exists', false, cb); 120 | port.close(function(err){ 121 | chai.assert.isDefined(err, 'err is not defined'); 122 | done(); 123 | }); 124 | }); 125 | 126 | it('flush errors when serialport not open', function (done) { 127 | var cb = function () {}; 128 | var port = new SerialPort('/dev/exists', false, cb); 129 | port.flush(function(err){ 130 | chai.assert.isDefined(err, 'err is not defined'); 131 | done(); 132 | }); 133 | }); 134 | 135 | it('set errors when serialport not open', function (done) { 136 | var cb = function () {}; 137 | var port = new SerialPort('/dev/exists', false, cb); 138 | port.set({}, function(err){ 139 | chai.assert.isDefined(err, 'err is not defined'); 140 | done(); 141 | }); 142 | }); 143 | 144 | it('drain errors when serialport not open', function (done) { 145 | var cb = function () {}; 146 | var port = new SerialPort('/dev/exists', false, cb); 147 | port.drain(function(err){ 148 | chai.assert.isDefined(err, 'err is not defined'); 149 | done(); 150 | }); 151 | }); 152 | 153 | }); 154 | 155 | describe('reading data', function () { 156 | 157 | it('emits data events by default', function (done) { 158 | var testData = new Buffer('I am a really short string'); 159 | var port = new SerialPort('/dev/exists', function () { 160 | port.once('data', function(recvData) { 161 | expect(recvData).to.eql(testData); 162 | done(); 163 | }); 164 | hardware.emitData('/dev/exists', testData); 165 | }); 166 | }); 167 | 168 | it('calls the dataCallback if set', function (done) { 169 | var testData = new Buffer('I am a really short string'); 170 | var opt = { 171 | dataCallback: function (recvData) { 172 | expect(recvData).to.eql(testData); 173 | done(); 174 | } 175 | }; 176 | var port = new SerialPort('/dev/exists', opt, function () { 177 | hardware.emitData('/dev/exists', testData); 178 | }); 179 | }); 180 | 181 | }); 182 | 183 | describe('#open', function () { 184 | 185 | it('passes the port to the bindings', function (done) { 186 | var openSpy = sandbox.spy(MockedSerialPort.SerialPortBinding, 'open'); 187 | var port = new SerialPort('/dev/exists', {}, false); 188 | port.open(function (err) { 189 | expect(err).to.not.be.ok; 190 | expect(openSpy.calledWith('/dev/exists')); 191 | done(); 192 | }); 193 | }); 194 | 195 | it('calls back an error when opening an invalid port', function (done) { 196 | var port = new SerialPort('/dev/unhappy', {}, false); 197 | port.open(function (err) { 198 | expect(err).to.be.ok; 199 | done(); 200 | }); 201 | }); 202 | 203 | it('emits data after being reopened', function (done) { 204 | var data = new Buffer('Howdy!'); 205 | var port = new SerialPort('/dev/exists', function () { 206 | port.close(); 207 | port.open(function () { 208 | port.once('data', function (res) { 209 | expect(res).to.eql(data); 210 | done(); 211 | }); 212 | hardware.emitData('/dev/exists', data); 213 | }); 214 | }); 215 | }); 216 | 217 | }); 218 | 219 | describe('close', function () { 220 | it('fires a close event when it\'s closed', function (done) { 221 | var port = new SerialPort('/dev/exists', function () { 222 | var closeSpy = sandbox.spy(); 223 | port.on('close', closeSpy); 224 | port.close(); 225 | expect(closeSpy.calledOnce); 226 | done(); 227 | }); 228 | }); 229 | 230 | it('fires a close event after being reopened', function (done) { 231 | var port = new SerialPort('/dev/exists', function () { 232 | var closeSpy = sandbox.spy(); 233 | port.on('close', closeSpy); 234 | port.close(); 235 | port.open(); 236 | port.close(); 237 | expect(closeSpy.calledTwice); 238 | done(); 239 | }); 240 | }); 241 | 242 | it('emits a close event', function (done) { 243 | var port = new SerialPort('/dev/exists', function () { 244 | port.on('close', function () { 245 | done(); 246 | }); 247 | port.close(); 248 | }); 249 | }); 250 | }); 251 | 252 | describe('disconnect', function () { 253 | it('fires a disconnect event', function (done) { 254 | var port = new SerialPort('/dev/exists', { 255 | disconnectedCallback: function (err) { 256 | expect(err).to.not.be.ok; 257 | done(); 258 | } 259 | }, function () { 260 | hardware.disconnect('/dev/exists'); 261 | }); 262 | }); 263 | 264 | it('emits a disconnect event', function (done) { 265 | var port = new SerialPort('/dev/exists', function () { 266 | port.on('disconnect', function () { 267 | done(); 268 | }); 269 | hardware.disconnect('/dev/exists'); 270 | }); 271 | }); 272 | }); 273 | 274 | }); 275 | 276 | -------------------------------------------------------------------------------- /src/serialport_win.cpp: -------------------------------------------------------------------------------- 1 | #include "serialport.h" 2 | #include 3 | #include "win/disphelper.h" 4 | 5 | #include "win/stdafx.h" 6 | #include "win/enumser.h" 7 | 8 | #include 9 | 10 | #ifdef WIN32 11 | 12 | #define MAX_BUFFER_SIZE 1000 13 | 14 | 15 | struct WindowsPlatformOptions : OpenBatonPlatformOptions 16 | { 17 | }; 18 | 19 | OpenBatonPlatformOptions* ParsePlatformOptions(const v8::Local& options){ 20 | // currently none 21 | return new WindowsPlatformOptions(); 22 | } 23 | 24 | 25 | // Declare type of pointer to CancelIoEx function 26 | typedef BOOL (WINAPI *CancelIoExType)(HANDLE hFile, LPOVERLAPPED lpOverlapped); 27 | 28 | 29 | std::list g_closingHandles; 30 | int bufferSize; 31 | void ErrorCodeToString(const char* prefix, int errorCode, char *errorStr) { 32 | switch(errorCode) { 33 | case ERROR_FILE_NOT_FOUND: 34 | _snprintf(errorStr, ERROR_STRING_SIZE, "%s: File not found", prefix); 35 | break; 36 | case ERROR_INVALID_HANDLE: 37 | _snprintf(errorStr, ERROR_STRING_SIZE, "%s: Invalid handle", prefix); 38 | break; 39 | case ERROR_ACCESS_DENIED: 40 | _snprintf(errorStr, ERROR_STRING_SIZE, "%s: Access denied", prefix); 41 | break; 42 | case ERROR_OPERATION_ABORTED: 43 | _snprintf(errorStr, ERROR_STRING_SIZE, "%s: operation aborted", prefix); 44 | break; 45 | default: 46 | _snprintf(errorStr, ERROR_STRING_SIZE, "%s: Unknown error code %d", prefix, errorCode); 47 | break; 48 | } 49 | } 50 | 51 | void EIO_Open(uv_work_t* req) { 52 | OpenBaton* data = static_cast(req->data); 53 | 54 | // data->path is char[1024] but on Windows it has the form "COMx\0" or "COMxx\0" 55 | // We want to prepend "\\\\.\\" to it before we call CreateFile 56 | strncpy(data->path + 20, data->path, 10); 57 | strncpy(data->path, "\\\\.\\", 4); 58 | strncpy(data->path + 4, data->path + 20, 10); 59 | 60 | HANDLE file = CreateFile( 61 | data->path, 62 | GENERIC_READ | GENERIC_WRITE, 63 | 0, 64 | NULL, 65 | OPEN_EXISTING, 66 | FILE_FLAG_OVERLAPPED, 67 | NULL); 68 | if (file == INVALID_HANDLE_VALUE) { 69 | DWORD errorCode = GetLastError(); 70 | char temp[100]; 71 | _snprintf(temp, sizeof(temp), "Opening %s", data->path); 72 | ErrorCodeToString(temp, errorCode, data->errorString); 73 | return; 74 | } 75 | 76 | bufferSize = data->bufferSize; 77 | if(bufferSize > MAX_BUFFER_SIZE) { 78 | bufferSize = MAX_BUFFER_SIZE; 79 | } 80 | 81 | DCB dcb = { 0 }; 82 | dcb.DCBlength = sizeof(DCB); 83 | if(data->hupcl == false) 84 | dcb.fDtrControl = DTR_CONTROL_DISABLE; // disable DTR to avoid reset 85 | if(!BuildCommDCB("9600,n,8,1", &dcb)) { 86 | ErrorCodeToString("BuildCommDCB", GetLastError(), data->errorString); 87 | return; 88 | } 89 | 90 | dcb.fBinary = true; 91 | dcb.BaudRate = data->baudRate; 92 | dcb.ByteSize = data->dataBits; 93 | switch(data->parity) { 94 | case SERIALPORT_PARITY_NONE: 95 | dcb.Parity = NOPARITY; 96 | break; 97 | case SERIALPORT_PARITY_MARK: 98 | dcb.Parity = MARKPARITY; 99 | break; 100 | case SERIALPORT_PARITY_EVEN: 101 | dcb.Parity = EVENPARITY; 102 | break; 103 | case SERIALPORT_PARITY_ODD: 104 | dcb.Parity = ODDPARITY; 105 | break; 106 | case SERIALPORT_PARITY_SPACE: 107 | dcb.Parity = SPACEPARITY; 108 | break; 109 | } 110 | switch(data->stopBits) { 111 | case SERIALPORT_STOPBITS_ONE: 112 | dcb.StopBits = ONESTOPBIT; 113 | break; 114 | case SERIALPORT_STOPBITS_ONE_FIVE: 115 | dcb.StopBits = ONE5STOPBITS; 116 | break; 117 | case SERIALPORT_STOPBITS_TWO: 118 | dcb.StopBits = TWOSTOPBITS; 119 | break; 120 | } 121 | 122 | if(!SetCommState(file, &dcb)) { 123 | ErrorCodeToString("SetCommState", GetLastError(), data->errorString); 124 | return; 125 | } 126 | 127 | // Set the com port read/write timeouts 128 | DWORD serialBitsPerByte = 8/*std data bits*/ + 1/*start bit*/; 129 | serialBitsPerByte += (data->parity == SERIALPORT_PARITY_NONE ) ? 0 : 1; 130 | serialBitsPerByte += (data->stopBits == SERIALPORT_STOPBITS_ONE) ? 1 : 2; 131 | DWORD msPerByte = (data->baudRate > 0) ? 132 | ((1000 * serialBitsPerByte + data->baudRate - 1) / data->baudRate) : 133 | 1; 134 | if (msPerByte < 1) { 135 | msPerByte = 1; 136 | } 137 | COMMTIMEOUTS commTimeouts = {0}; 138 | commTimeouts.ReadIntervalTimeout = msPerByte; // Minimize chance of concatenating of separate serial port packets on read 139 | commTimeouts.ReadTotalTimeoutMultiplier = 0; // Do not allow big read timeout when big read buffer used 140 | commTimeouts.ReadTotalTimeoutConstant = 1000; // Total read timeout (period of read loop) 141 | commTimeouts.WriteTotalTimeoutConstant = 1000; // Const part of write timeout 142 | commTimeouts.WriteTotalTimeoutMultiplier = msPerByte; // Variable part of write timeout (per byte) 143 | if(!SetCommTimeouts(file, &commTimeouts)) { 144 | ErrorCodeToString("SetCommTimeouts", GetLastError(), data->errorString); 145 | return; 146 | } 147 | 148 | // Remove garbage data in RX/TX queues 149 | PurgeComm(file, PURGE_RXCLEAR); 150 | PurgeComm(file, PURGE_TXCLEAR); 151 | 152 | data->result = (int)file; 153 | } 154 | 155 | struct WatchPortBaton { 156 | public: 157 | HANDLE fd; 158 | DWORD bytesRead; 159 | char buffer[MAX_BUFFER_SIZE]; 160 | char errorString[ERROR_STRING_SIZE]; 161 | DWORD errorCode; 162 | bool disconnected; 163 | Nan::Callback* dataCallback; 164 | Nan::Callback* errorCallback; 165 | Nan::Callback* disconnectedCallback; 166 | }; 167 | 168 | void EIO_Update(uv_work_t* req) { 169 | 170 | } 171 | 172 | 173 | void EIO_Set(uv_work_t* req) { 174 | SetBaton* data = static_cast(req->data); 175 | 176 | if (data->rts) { 177 | EscapeCommFunction((HANDLE)data->fd, SETRTS); 178 | }else{ 179 | EscapeCommFunction((HANDLE)data->fd, CLRRTS); 180 | } 181 | 182 | if (data->dtr) { 183 | EscapeCommFunction((HANDLE)data->fd, SETDTR); 184 | }else{ 185 | EscapeCommFunction((HANDLE)data->fd, CLRDTR); 186 | } 187 | 188 | if (data->brk) { 189 | EscapeCommFunction((HANDLE)data->fd, SETBREAK); 190 | }else{ 191 | EscapeCommFunction((HANDLE)data->fd, CLRBREAK); 192 | } 193 | 194 | DWORD bits = 0; 195 | 196 | GetCommMask((HANDLE)data->fd, &bits); 197 | 198 | bits &= ~( EV_CTS | EV_DSR); 199 | 200 | if (data->cts) { 201 | bits |= EV_CTS; 202 | } 203 | 204 | if (data->dsr) { 205 | bits |= EV_DSR; 206 | } 207 | 208 | data->result = SetCommMask((HANDLE)data->fd, bits); 209 | } 210 | 211 | 212 | void EIO_WatchPort(uv_work_t* req) { 213 | WatchPortBaton* data = static_cast(req->data); 214 | data->bytesRead = 0; 215 | data->disconnected = false; 216 | 217 | // Event used by GetOverlappedResult(..., TRUE) to wait for incoming data or timeout 218 | // Event MUST be used if program has several simultaneous asynchronous operations 219 | // on the same handle (i.e. ReadFile and WriteFile) 220 | HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); 221 | 222 | while(true) { 223 | OVERLAPPED ov = {0}; 224 | ov.hEvent = hEvent; 225 | 226 | // Start read operation - synchrounous or asynchronous 227 | DWORD bytesReadSync = 0; 228 | if(!ReadFile((HANDLE)data->fd, data->buffer, bufferSize, &bytesReadSync, &ov)) { 229 | data->errorCode = GetLastError(); 230 | if(data->errorCode != ERROR_IO_PENDING) { 231 | // Read operation error 232 | if(data->errorCode == ERROR_OPERATION_ABORTED) { 233 | data->disconnected = true; 234 | } 235 | else { 236 | ErrorCodeToString("Reading from COM port (ReadFile)", data->errorCode, data->errorString); 237 | } 238 | break; 239 | } 240 | 241 | // Read operation is asynchronous and is pending 242 | // We MUST wait for operation completion before deallocation of OVERLAPPED struct 243 | // or read data buffer 244 | 245 | // Wait for async read operation completion or timeout 246 | DWORD bytesReadAsync = 0; 247 | if(!GetOverlappedResult((HANDLE)data->fd, &ov, &bytesReadAsync, TRUE)) { 248 | // Read operation error 249 | data->errorCode = GetLastError(); 250 | if(data->errorCode == ERROR_OPERATION_ABORTED) { 251 | data->disconnected = true; 252 | } 253 | else { 254 | ErrorCodeToString("Reading from COM port (GetOverlappedResult)", data->errorCode, data->errorString); 255 | } 256 | break; 257 | } 258 | else { 259 | // Read operation completed asynchronously 260 | data->bytesRead = bytesReadAsync; 261 | } 262 | } 263 | else { 264 | // Read operation completed synchronously 265 | data->bytesRead = bytesReadSync; 266 | } 267 | 268 | // Return data received if any 269 | if(data->bytesRead > 0) { 270 | break; 271 | } 272 | } 273 | 274 | CloseHandle(hEvent); 275 | } 276 | 277 | bool IsClosingHandle(int fd) { 278 | for(std::list::iterator it=g_closingHandles.begin(); it!=g_closingHandles.end(); ++it) { 279 | if(fd == *it) { 280 | g_closingHandles.remove(fd); 281 | return true; 282 | } 283 | } 284 | return false; 285 | } 286 | 287 | void DisposeWatchPortCallbacks(WatchPortBaton* data) { 288 | delete data->dataCallback; 289 | delete data->errorCallback; 290 | delete data->disconnectedCallback; 291 | } 292 | 293 | void EIO_AfterWatchPort(uv_work_t* req) { 294 | Nan::HandleScope scope; 295 | 296 | WatchPortBaton* data = static_cast(req->data); 297 | if(data->disconnected) { 298 | data->disconnectedCallback->Call(0, NULL); 299 | DisposeWatchPortCallbacks(data); 300 | goto cleanup; 301 | } 302 | 303 | if(data->bytesRead > 0) { 304 | v8::Local argv[1]; 305 | argv[0] = Nan::NewBuffer(data->buffer, data->bytesRead).ToLocalChecked(); 306 | data->dataCallback->Call(1, argv); 307 | } else if(data->errorCode > 0) { 308 | if(data->errorCode == ERROR_INVALID_HANDLE && IsClosingHandle((int)data->fd)) { 309 | DisposeWatchPortCallbacks(data); 310 | goto cleanup; 311 | } else { 312 | v8::Local argv[1]; 313 | argv[0] = Nan::Error(data->errorString); 314 | data->errorCallback->Call(1, argv); 315 | Sleep(100); // prevent the errors from occurring too fast 316 | } 317 | } 318 | AfterOpenSuccess((int)data->fd, data->dataCallback, data->disconnectedCallback, data->errorCallback); 319 | 320 | cleanup: 321 | delete data; 322 | delete req; 323 | } 324 | 325 | void AfterOpenSuccess(int fd, Nan::Callback* dataCallback, Nan::Callback* disconnectedCallback, Nan::Callback* errorCallback) { 326 | WatchPortBaton* baton = new WatchPortBaton(); 327 | memset(baton, 0, sizeof(WatchPortBaton)); 328 | baton->fd = (HANDLE)fd; 329 | baton->dataCallback = dataCallback; 330 | baton->errorCallback = errorCallback; 331 | baton->disconnectedCallback = disconnectedCallback; 332 | 333 | uv_work_t* req = new uv_work_t(); 334 | req->data = baton; 335 | 336 | uv_queue_work(uv_default_loop(), req, EIO_WatchPort, (uv_after_work_cb)EIO_AfterWatchPort); 337 | } 338 | 339 | void EIO_Write(uv_work_t* req) { 340 | QueuedWrite* queuedWrite = static_cast(req->data); 341 | WriteBaton* data = static_cast(queuedWrite->baton); 342 | data->result = 0; 343 | 344 | do { 345 | OVERLAPPED ov = {0}; 346 | // Event used by GetOverlappedResult(..., TRUE) to wait for outgoing data or timeout 347 | // Event MUST be used if program has several simultaneous asynchronous operations 348 | // on the same handle (i.e. ReadFile and WriteFile) 349 | ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); 350 | 351 | // Start write operation - synchrounous or asynchronous 352 | DWORD bytesWrittenSync = 0; 353 | if(!WriteFile((HANDLE)data->fd, data->bufferData, static_cast(data->bufferLength), &bytesWrittenSync, &ov)) { 354 | DWORD lastError = GetLastError(); 355 | if(lastError != ERROR_IO_PENDING) { 356 | // Write operation error 357 | ErrorCodeToString("Writing to COM port (WriteFile)", lastError, data->errorString); 358 | } 359 | else { 360 | // Write operation is asynchronous and is pending 361 | // We MUST wait for operation completion before deallocation of OVERLAPPED struct 362 | // or write data buffer 363 | 364 | // Wait for async write operation completion or timeout 365 | DWORD bytesWrittenAsync = 0; 366 | if(!GetOverlappedResult((HANDLE)data->fd, &ov, &bytesWrittenAsync, TRUE)) { 367 | // Write operation error 368 | DWORD lastError = GetLastError(); 369 | ErrorCodeToString("Writing to COM port (GetOverlappedResult)", lastError, data->errorString); 370 | } 371 | else { 372 | // Write operation completed asynchronously 373 | data->result = bytesWrittenAsync; 374 | } 375 | } 376 | } 377 | else { 378 | // Write operation completed synchronously 379 | data->result = bytesWrittenSync; 380 | } 381 | 382 | data->offset += data->result; 383 | CloseHandle(ov.hEvent); 384 | } while (data->bufferLength > data->offset); 385 | 386 | } 387 | 388 | void EIO_Close(uv_work_t* req) { 389 | CloseBaton* data = static_cast(req->data); 390 | 391 | g_closingHandles.push_back(data->fd); 392 | 393 | HMODULE hKernel32 = LoadLibrary("kernel32.dll"); 394 | // Look up function address 395 | CancelIoExType pCancelIoEx = (CancelIoExType)GetProcAddress(hKernel32, "CancelIoEx"); 396 | // Do something with it 397 | if (pCancelIoEx) 398 | { 399 | // Function exists so call it 400 | // Cancel all pending IO Requests for the current device 401 | pCancelIoEx((HANDLE)data->fd, NULL); 402 | } 403 | if(!CloseHandle((HANDLE)data->fd)) { 404 | ErrorCodeToString("closing connection", GetLastError(), data->errorString); 405 | return; 406 | } 407 | } 408 | 409 | /* 410 | * listComPorts.c -- list COM ports 411 | * 412 | * http://github.com/todbot/usbSearch/ 413 | * 414 | * 2012, Tod E. Kurt, http://todbot.com/blog/ 415 | * 416 | * 417 | * Uses DispHealper : http://disphelper.sourceforge.net/ 418 | * 419 | * Notable VIDs & PIDs combos: 420 | * VID 0403 - FTDI 421 | * 422 | * VID 0403 / PID 6001 - Arduino Diecimila 423 | * 424 | */ 425 | void EIO_List(uv_work_t* req) { 426 | ListBaton* data = static_cast(req->data); 427 | 428 | { 429 | DISPATCH_OBJ(wmiSvc); 430 | DISPATCH_OBJ(colDevices); 431 | 432 | dhInitialize(TRUE); 433 | dhToggleExceptions(FALSE); 434 | 435 | dhGetObject(L"winmgmts:{impersonationLevel=impersonate}!\\\\.\\root\\cimv2", NULL, &wmiSvc); 436 | dhGetValue(L"%o", &colDevices, wmiSvc, L".ExecQuery(%S)", L"Select * from Win32_PnPEntity"); 437 | 438 | int port_count = 0; 439 | FOR_EACH(objDevice, colDevices, NULL) { 440 | char* name = NULL; 441 | char* pnpid = NULL; 442 | char* manu = NULL; 443 | char* match; 444 | 445 | dhGetValue(L"%s", &name, objDevice, L".Name"); 446 | dhGetValue(L"%s", &pnpid, objDevice, L".PnPDeviceID"); 447 | 448 | if( name != NULL && ((match = strstr( name, "(COM" )) != NULL) ) { // look for "(COM23)" 449 | // 'Manufacturuer' can be null, so only get it if we need it 450 | dhGetValue(L"%s", &manu, objDevice, L".Manufacturer"); 451 | port_count++; 452 | char* comname = strtok( match, "()"); 453 | ListResultItem* resultItem = new ListResultItem(); 454 | resultItem->comName = comname; 455 | resultItem->manufacturer = manu; 456 | resultItem->pnpId = pnpid; 457 | data->results.push_back(resultItem); 458 | dhFreeString(manu); 459 | } 460 | 461 | dhFreeString(name); 462 | dhFreeString(pnpid); 463 | } NEXT(objDevice); 464 | 465 | SAFE_RELEASE(colDevices); 466 | SAFE_RELEASE(wmiSvc); 467 | 468 | dhUninitialize(TRUE); 469 | } 470 | 471 | std::vector ports; 472 | if (CEnumerateSerial::UsingQueryDosDevice(ports)) 473 | { 474 | for (size_t i = 0; i < ports.size(); i++) 475 | { 476 | char comname[64] = { 0 }; 477 | _snprintf(comname, sizeof(comname), "COM%u", ports[i]); 478 | bool bFound = false; 479 | for (std::list::iterator ri = data->results.begin(); ri != data->results.end(); ++ri) 480 | { 481 | if (stricmp((*ri)->comName.c_str(), comname) == 0) 482 | { 483 | bFound = true; 484 | break; 485 | } 486 | } 487 | if (!bFound) 488 | { 489 | ListResultItem* resultItem = new ListResultItem(); 490 | resultItem->comName = comname; 491 | resultItem->manufacturer = ""; 492 | resultItem->pnpId = ""; 493 | data->results.push_back(resultItem); 494 | } 495 | } 496 | } 497 | } 498 | 499 | void EIO_Flush(uv_work_t* req) { 500 | FlushBaton* data = static_cast(req->data); 501 | 502 | if(!FlushFileBuffers((HANDLE)data->fd)) { 503 | ErrorCodeToString("flushing connection", GetLastError(), data->errorString); 504 | return; 505 | } 506 | } 507 | 508 | void EIO_Drain(uv_work_t* req) { 509 | DrainBaton* data = static_cast(req->data); 510 | 511 | if(!FlushFileBuffers((HANDLE)data->fd)) { 512 | ErrorCodeToString("draining connection", GetLastError(), data->errorString); 513 | return; 514 | } 515 | } 516 | 517 | #endif 518 | -------------------------------------------------------------------------------- /serialport.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Copyright 2011 Chris Williams 4 | 5 | // Require serialport binding from pre-compiled binaries using 6 | // node-pre-gyp, if something fails or package not available fallback 7 | // to regular build from source. 8 | 9 | var debug = require('debug')('serialport'); 10 | var path = require('path'); 11 | var PACKAGE_JSON = path.join(__dirname, 'package.json'); 12 | var SerialPortBinding = require('./serialport.node'); 13 | 14 | var parsers = require('./parsers'); 15 | var EventEmitter = require('events').EventEmitter; 16 | var util = require('util'); 17 | var fs = require('fs'); 18 | var stream = require('stream'); 19 | var path = require('path'); 20 | var async = require('async'); 21 | var exec = require('child_process').exec; 22 | 23 | function SerialPortFactory(_spfOptions) { 24 | _spfOptions = _spfOptions || {}; 25 | 26 | var spfOptions = {}; 27 | 28 | spfOptions.queryPortsByPath = (_spfOptions.queryPortsByPath === true ? true : false); 29 | 30 | var factory = this; 31 | 32 | // Removing check for valid BaudRates due to ticket: #140 33 | // var BAUDRATES = [500000, 230400, 115200, 57600, 38400, 19200, 9600, 4800, 2400, 1800, 1200, 600, 300, 200, 150, 134, 110, 75, 50]; 34 | 35 | // VALIDATION ARRAYS 36 | var DATABITS = [5, 6, 7, 8]; 37 | var STOPBITS = [1, 1.5, 2]; 38 | var PARITY = ['none', 'even', 'mark', 'odd', 'space']; 39 | var FLOWCONTROLS = ['XON', 'XOFF', 'XANY', 'RTSCTS']; 40 | var SETS = ['rts', 'cts', 'dtr', 'dts', 'brk']; 41 | 42 | 43 | // Stuff from ReadStream, refactored for our usage: 44 | var kPoolSize = 40 * 1024; 45 | var kMinPoolSpace = 128; 46 | 47 | function makeDefaultPlatformOptions(){ 48 | var options = {}; 49 | 50 | if (process.platform !== 'win32') { 51 | options.vmin = 1; 52 | options.vtime = 0; 53 | } 54 | 55 | return options; 56 | } 57 | 58 | // The default options, can be overwritten in the 'SerialPort' constructor 59 | var _options = { 60 | baudrate: 9600, 61 | parity: 'none', 62 | rtscts: false, 63 | xon: false, 64 | xoff: false, 65 | xany: false, 66 | hupcl:true, 67 | rts: true, 68 | cts: false, 69 | dtr: true, 70 | dts: false, 71 | brk: false, 72 | databits: 8, 73 | stopbits: 1, 74 | buffersize: 256, 75 | parser: parsers.raw, 76 | platformOptions: makeDefaultPlatformOptions() 77 | }; 78 | 79 | function SerialPort(path, options, openImmediately, callback) { 80 | 81 | var self = this; 82 | 83 | var args = Array.prototype.slice.call(arguments); 84 | callback = args.pop(); 85 | if (typeof (callback) !== 'function') { 86 | callback = null; 87 | } 88 | options = (typeof options !== 'function') && options || {}; 89 | 90 | var opts = {}; 91 | 92 | openImmediately = (openImmediately === undefined || openImmediately === null) ? true : openImmediately; 93 | 94 | stream.Stream.call(this); 95 | 96 | callback = callback || function (err) { 97 | if (err) { 98 | if (self._events.error) { 99 | self.emit('error', err); 100 | } else { 101 | factory.emit('error', err); 102 | } 103 | } 104 | }; 105 | 106 | var err; 107 | 108 | 109 | opts.baudRate = options.baudRate || options.baudrate || _options.baudrate; 110 | 111 | opts.dataBits = options.dataBits || options.databits || _options.databits; 112 | if (DATABITS.indexOf(opts.dataBits) === -1) { 113 | err = new Error('Invalid "databits": ' + opts.dataBits); 114 | callback(err); 115 | return; 116 | } 117 | 118 | opts.stopBits = options.stopBits || options.stopbits || _options.stopbits; 119 | if (STOPBITS.indexOf(opts.stopBits) === -1) { 120 | err = new Error('Invalid "stopbits": ' + opts.stopbits); 121 | callback(err); 122 | return; 123 | } 124 | 125 | opts.parity = options.parity || _options.parity; 126 | if (PARITY.indexOf(opts.parity) === -1) { 127 | err = new Error('Invalid "parity": ' + opts.parity); 128 | callback(err); 129 | return; 130 | } 131 | if (!path) { 132 | err = new Error('Invalid port specified: ' + path); 133 | callback(err); 134 | return; 135 | } 136 | 137 | // flush defaults, then update with provided details 138 | opts.rtscts = _options.rtscts; 139 | opts.xon = _options.xon; 140 | opts.xoff = _options.xoff; 141 | opts.xany = _options.xany; 142 | 143 | if (options.flowControl || options.flowcontrol) { 144 | var fc = options.flowControl || options.flowcontrol; 145 | 146 | if (typeof fc === 'boolean') { 147 | opts.rtscts = true; 148 | } else { 149 | var clean = fc.every(function (flowControl) { 150 | var fcup = flowControl.toUpperCase(); 151 | var idx = FLOWCONTROLS.indexOf(fcup); 152 | if (idx < 0) { 153 | var err = new Error('Invalid "flowControl": ' + fcup + '. Valid options: ' + FLOWCONTROLS.join(', ')); 154 | callback(err); 155 | return false; 156 | } else { 157 | 158 | // "XON", "XOFF", "XANY", "DTRDTS", "RTSCTS" 159 | switch (idx) { 160 | case 0: opts.xon = true; break; 161 | case 1: opts.xoff = true; break; 162 | case 2: opts.xany = true; break; 163 | case 3: opts.rtscts = true; break; 164 | } 165 | return true; 166 | } 167 | }); 168 | if(!clean){ 169 | return; 170 | } 171 | } 172 | } 173 | 174 | opts.bufferSize = options.bufferSize || options.buffersize || _options.buffersize; 175 | opts.parser = options.parser || _options.parser; 176 | opts.platformOptions = options.platformOptions || _options.platformOptions; 177 | options.hupcl = (typeof options.hupcl === 'boolean') ? options.hupcl : _options.hupcl; 178 | opts.dataCallback = options.dataCallback || function (data) { 179 | opts.parser(self, data); 180 | }; 181 | 182 | opts.disconnectedCallback = options.disconnectedCallback || function (err) { 183 | if (self.closing) { 184 | return; 185 | } 186 | if (!err) { 187 | err = new Error('Disconnected'); 188 | } 189 | self.emit('disconnect', err); 190 | }; 191 | 192 | if (process.platform !== 'win32') { 193 | // All other platforms: 194 | this.fd = null; 195 | this.paused = true; 196 | this.bufferSize = options.bufferSize || 64 * 1024; 197 | this.readable = true; 198 | this.reading = false; 199 | } 200 | 201 | this.options = opts; 202 | this.path = path; 203 | if (openImmediately) { 204 | process.nextTick(function () { 205 | self.open(callback); 206 | }); 207 | } 208 | } 209 | 210 | util.inherits(SerialPort, stream.Stream); 211 | 212 | 213 | SerialPort.prototype.open = function (callback) { 214 | var self = this; 215 | this.paused = true; 216 | this.readable = true; 217 | this.reading = false; 218 | factory.SerialPortBinding.open(this.path, this.options, function (err, fd) { 219 | self.fd = fd; 220 | if (err) { 221 | if (callback) { 222 | callback(err); 223 | } else { 224 | self.emit('error', err); 225 | } 226 | return; 227 | } 228 | if (process.platform !== 'win32') { 229 | self.paused = false; 230 | self.serialPoller = new factory.SerialPortBinding.SerialportPoller(self.fd, function (err) { 231 | if (!err) { 232 | self._read(); 233 | } else { 234 | self.disconnected(err); 235 | } 236 | }); 237 | self.serialPoller.start(); 238 | } 239 | 240 | self.emit('open'); 241 | if (callback) { callback(); } 242 | }); 243 | }; 244 | 245 | //underlying code is written to update all options, but for now 246 | //only baud is respected as I dont want to duplicate all the option 247 | //verification code above 248 | SerialPort.prototype.update = function (options, callback) { 249 | var self = this; 250 | if (!this.fd) { 251 | debug('Update attempted, but serialport not available - FD is not set'); 252 | var err = new Error('Serialport not open.'); 253 | if (callback) { 254 | callback(err); 255 | } else { 256 | // console.log("write-fd"); 257 | self.emit('error', err); 258 | } 259 | return; 260 | } 261 | 262 | this.options.baudRate = options.baudRate || options.baudrate || _options.baudrate; 263 | 264 | factory.SerialPortBinding.update(this.fd, this.options, function (err) { 265 | if (err) { 266 | if (callback) { 267 | callback(err); 268 | } else { 269 | self.emit('error', err); 270 | } 271 | return; 272 | } 273 | self.emit('open'); 274 | if (callback) { callback(); } 275 | }); 276 | }; 277 | 278 | SerialPort.prototype.isOpen = function() { 279 | return (this.fd ? true : false); 280 | }; 281 | 282 | SerialPort.prototype.write = function (buffer, callback) { 283 | var self = this; 284 | if (!this.fd) { 285 | debug('Write attempted, but serialport not available - FD is not set'); 286 | var err = new Error('Serialport not open.'); 287 | if (callback) { 288 | callback(err); 289 | } else { 290 | // console.log("write-fd"); 291 | self.emit('error', err); 292 | } 293 | return; 294 | } 295 | 296 | if (!Buffer.isBuffer(buffer)) { 297 | buffer = new Buffer(buffer); 298 | } 299 | debug('Write: '+JSON.stringify(buffer)); 300 | factory.SerialPortBinding.write(this.fd, buffer, function (err, results) { 301 | if (callback) { 302 | callback(err, results); 303 | } else { 304 | if (err) { 305 | // console.log("write"); 306 | self.emit('error', err); 307 | } 308 | } 309 | }); 310 | }; 311 | 312 | if (process.platform !== 'win32') { 313 | SerialPort.prototype._read = function () { 314 | var self = this; 315 | 316 | // console.log(">>READ"); 317 | if (!self.readable || self.paused || self.reading) { 318 | return; 319 | } 320 | 321 | self.reading = true; 322 | 323 | if (!self.pool || self.pool.length - self.pool.used < kMinPoolSpace) { 324 | // discard the old pool. Can't add to the free list because 325 | // users might have refernces to slices on it. 326 | self.pool = null; 327 | 328 | // alloc new pool 329 | self.pool = new Buffer(kPoolSize); 330 | self.pool.used = 0; 331 | } 332 | 333 | // Grab another reference to the pool in the case that while we're in the 334 | // thread pool another read() finishes up the pool, and allocates a new 335 | // one. 336 | var toRead = Math.min(self.pool.length - self.pool.used, ~~self.bufferSize); 337 | var start = self.pool.used; 338 | 339 | function afterRead(err, bytesRead, readPool, bytesRequested) { 340 | self.reading = false; 341 | if (err) { 342 | if (err.code && err.code === 'EAGAIN') { 343 | if (self.fd >= 0) { 344 | self.serialPoller.start(); 345 | } 346 | } else if (err.code && (err.code === 'EBADF' || err.code === 'ENXIO' || (err.errno === -1 || err.code === 'UNKNOWN'))) { // handle edge case were mac/unix doesn't clearly know the error. 347 | self.disconnected(err); 348 | } else { 349 | self.fd = null; 350 | self.emit('error', err); 351 | self.readable = false; 352 | } 353 | } else { 354 | // Since we will often not read the number of bytes requested, 355 | // let's mark the ones we didn't need as available again. 356 | self.pool.used -= bytesRequested - bytesRead; 357 | 358 | if (bytesRead === 0) { 359 | if (self.fd >= 0) { 360 | self.serialPoller.start(); 361 | } 362 | } else { 363 | var b = self.pool.slice(start, start + bytesRead); 364 | 365 | // do not emit events if the stream is paused 366 | if (self.paused) { 367 | self.buffer = Buffer.concat([self.buffer, b]); 368 | return; 369 | } else { 370 | self._emitData(b); 371 | } 372 | 373 | // do not emit events anymore after we declared the stream unreadable 374 | if (!self.readable) { 375 | return; 376 | } 377 | self._read(); 378 | } 379 | } 380 | 381 | } 382 | 383 | fs.read(self.fd, self.pool, self.pool.used, toRead, null, function (err, bytesRead) { 384 | var readPool = self.pool; 385 | var bytesRequested = toRead; 386 | afterRead(err, bytesRead, readPool, bytesRequested); 387 | }); 388 | 389 | self.pool.used += toRead; 390 | }; 391 | 392 | 393 | SerialPort.prototype._emitData = function (data) { 394 | this.options.dataCallback(data); 395 | }; 396 | 397 | SerialPort.prototype.pause = function () { 398 | var self = this; 399 | self.paused = true; 400 | }; 401 | 402 | SerialPort.prototype.resume = function () { 403 | var self = this; 404 | self.paused = false; 405 | 406 | if (self.buffer) { 407 | var buffer = self.buffer; 408 | self.buffer = null; 409 | self._emitData(buffer); 410 | } 411 | 412 | // No longer open? 413 | if (null === self.fd) { 414 | return; 415 | } 416 | 417 | self._read(); 418 | }; 419 | 420 | } // if !'win32' 421 | 422 | 423 | SerialPort.prototype.disconnected = function (err) { 424 | var self = this; 425 | var fd = self.fd; 426 | 427 | // send notification of disconnect 428 | if (self.options.disconnectedCallback) { 429 | self.options.disconnectedCallback(err); 430 | } else { 431 | self.emit('disconnect', err); 432 | } 433 | self.paused = true; 434 | self.closing = true; 435 | 436 | self.emit('close'); 437 | 438 | // clean up all other items 439 | fd = self.fd; 440 | 441 | try { 442 | factory.SerialPortBinding.close(fd, function (err) { 443 | if (err) { 444 | debug('Disconnect completed with error: '+JSON.stringify(err)); 445 | } else { 446 | debug('Disconnect completed.'); 447 | } 448 | }); 449 | } catch (e) { 450 | debug('Disconnect completed with an exception: '+JSON.stringify(e)); 451 | } 452 | 453 | self.removeAllListeners(); 454 | self.closing = false; 455 | self.fd = 0; 456 | 457 | if (process.platform !== 'win32') { 458 | self.readable = false; 459 | self.serialPoller.close(); 460 | } 461 | 462 | }; 463 | 464 | 465 | SerialPort.prototype.close = function (callback) { 466 | var self = this; 467 | 468 | var fd = self.fd; 469 | 470 | if (self.closing) { 471 | return; 472 | } 473 | if (!fd) { 474 | var err = new Error('Serialport not open.'); 475 | if (callback) { 476 | callback(err); 477 | } else { 478 | // console.log("sp not open"); 479 | self.emit('error', err); 480 | } 481 | return; 482 | } 483 | 484 | self.closing = true; 485 | 486 | // Stop polling before closing the port. 487 | if (process.platform !== 'win32') { 488 | self.readable = false; 489 | self.serialPoller.close(); 490 | } 491 | 492 | try { 493 | factory.SerialPortBinding.close(fd, function (err) { 494 | 495 | if (err) { 496 | if (callback) { 497 | callback(err); 498 | } else { 499 | // console.log("doclose"); 500 | self.emit('error', err); 501 | } 502 | return; 503 | } 504 | 505 | self.emit('close'); 506 | self.removeAllListeners(); 507 | self.closing = false; 508 | self.fd = 0; 509 | 510 | if (callback) { 511 | callback(); 512 | } 513 | }); 514 | } catch (ex) { 515 | self.closing = false; 516 | if (callback) { 517 | callback(ex); 518 | } else { 519 | self.emit('error', ex); 520 | } 521 | } 522 | }; 523 | 524 | function listUnix(callback) { 525 | function udev_parser(udev_output, callback) { 526 | function udev_output_to_json(output) { 527 | var result = {}; 528 | var lines = output.split('\n'); 529 | for (var i = 0; i < lines.length; i++) { 530 | var line = lines[i].trim(); 531 | if (line !== '') { 532 | var line_parts = lines[i].split('='); 533 | result[line_parts[0].trim()] = line_parts[1].trim(); 534 | } 535 | } 536 | return result; 537 | } 538 | var as_json = udev_output_to_json(udev_output); 539 | var pnpId = as_json.DEVLINKS.split(' ')[0]; 540 | pnpId = pnpId.substring(pnpId.lastIndexOf('/') + 1); 541 | var port = { 542 | comName: as_json.DEVNAME, 543 | manufacturer: as_json.ID_VENDOR, 544 | serialNumber: as_json.ID_SERIAL, 545 | pnpId: pnpId, 546 | vendorId: '0x' + as_json.ID_VENDOR_ID, 547 | productId: '0x' + as_json.ID_MODEL_ID 548 | }; 549 | 550 | callback(null, port); 551 | } 552 | 553 | var dirName = (spfOptions.queryPortsByPath ? '/dev/serial/by-path' : '/dev/serial/by-id'); 554 | 555 | fs.readdir(dirName, function (err, files) { 556 | if (err) { 557 | // if this directory is not found this could just be because it's not plugged in 558 | if (err.errno === 34) { 559 | return callback(null, []); 560 | } 561 | 562 | if (callback) { 563 | callback(err); 564 | } else { 565 | factory.emit('error', err); 566 | } 567 | return; 568 | } 569 | 570 | async.map(files, function (file, callback) { 571 | var fileName = path.join(dirName, file); 572 | fs.readlink(fileName, function (err, link) { 573 | if (err) { 574 | if (callback) { 575 | callback(err); 576 | } else { 577 | factory.emit('error', err); 578 | } 579 | return; 580 | } 581 | 582 | link = path.resolve(dirName, link); 583 | exec('/sbin/udevadm info --query=property -p $(/sbin/udevadm info -q path -n ' + link + ')', function (err, stdout) { 584 | if (err) { 585 | if (callback) { 586 | callback(err); 587 | } else { 588 | factory.emit('error', err); 589 | } 590 | return; 591 | } 592 | 593 | udev_parser(stdout, callback); 594 | }); 595 | }); 596 | }, callback); 597 | }); 598 | } 599 | 600 | SerialPort.prototype.flush = function (callback) { 601 | var self = this; 602 | var fd = self.fd; 603 | 604 | if (!fd) { 605 | var err = new Error('Serialport not open.'); 606 | if (callback) { 607 | callback(err); 608 | } else { 609 | self.emit('error', err); 610 | } 611 | return; 612 | } 613 | 614 | factory.SerialPortBinding.flush(fd, function (err, result) { 615 | if (err) { 616 | if (callback) { 617 | callback(err, result); 618 | } else { 619 | self.emit('error', err); 620 | } 621 | } else { 622 | if (callback) { 623 | callback(err, result); 624 | } 625 | } 626 | }); 627 | }; 628 | 629 | SerialPort.prototype.set = function (options, callback) { 630 | var self = this; 631 | var fd = self.fd; 632 | 633 | options = (typeof option !== 'function') && options || {}; 634 | 635 | // flush defaults, then update with provided details 636 | 637 | if(!options.hasOwnProperty('rts')){ 638 | options.rts = _options.rts; 639 | } 640 | if(!options.hasOwnProperty('dtr')){ 641 | options.dtr = _options.dtr; 642 | } 643 | if(!options.hasOwnProperty('cts')){ 644 | options.cts = _options.cts; 645 | } 646 | if(!options.hasOwnProperty('dts')){ 647 | options.dts = _options.dts; 648 | } 649 | if(!options.hasOwnProperty('brk')){ 650 | options.brk = _options.brk; 651 | } 652 | 653 | if (!fd) { 654 | var err = new Error('Serialport not open.'); 655 | if (callback) { 656 | callback(err); 657 | } else { 658 | self.emit('error', err); 659 | } 660 | return; 661 | } 662 | 663 | factory.SerialPortBinding.set(fd, options, function (err, result) { 664 | if (err) { 665 | if (callback) { 666 | callback(err, result); 667 | } else { 668 | self.emit('error', err); 669 | } 670 | } else { 671 | callback(err, result); 672 | } 673 | }); 674 | }; 675 | 676 | SerialPort.prototype.drain = function (callback) { 677 | var self = this; 678 | var fd = this.fd; 679 | 680 | if (!fd) { 681 | var err = new Error('Serialport not open.'); 682 | if (callback) { 683 | callback(err); 684 | } else { 685 | self.emit('error', err); 686 | } 687 | return; 688 | } 689 | 690 | factory.SerialPortBinding.drain(fd, function (err, result) { 691 | if (err) { 692 | if (callback) { 693 | callback(err, result); 694 | } else { 695 | self.emit('error', err); 696 | } 697 | } else { 698 | if (callback) { 699 | callback(err, result); 700 | } 701 | } 702 | }); 703 | }; 704 | 705 | factory.SerialPort = SerialPort; 706 | factory.parsers = parsers; 707 | factory.SerialPortBinding = SerialPortBinding; 708 | 709 | if (process.platform === 'win32') { 710 | factory.list = SerialPortBinding.list; 711 | } else if (process.platform === 'darwin') { 712 | factory.list = SerialPortBinding.list; 713 | } else { 714 | factory.list = listUnix; 715 | } 716 | 717 | } 718 | 719 | util.inherits(SerialPortFactory, EventEmitter); 720 | 721 | module.exports = new SerialPortFactory(); 722 | -------------------------------------------------------------------------------- /src/win/disphelper.h: -------------------------------------------------------------------------------- 1 | /* This file is part of the source code for the DispHelper COM helper library. 2 | * DispHelper allows you to call COM objects with an extremely simple printf style syntax. 3 | * DispHelper can be used from C++ or even plain C. It works with most Windows compilers 4 | * including Dev-CPP, Visual C++ and LCC-WIN32. Including DispHelper in your project 5 | * couldn't be simpler as it is available in a compacted single file version. 6 | * 7 | * Included with DispHelper are over 20 samples that demonstrate using COM objects 8 | * including ADO, CDO, Outlook, Eudora, Excel, Word, Internet Explorer, MSHTML, 9 | * PocketSoap, Word Perfect, MS Agent, SAPI, MSXML, WIA, dexplorer and WMI. 10 | * 11 | * DispHelper is free open source software provided under the BSD license. 12 | * 13 | * Find out more and download DispHelper at: 14 | * http://sourceforge.net/projects/disphelper/ 15 | * http://disphelper.sourceforge.net/ 16 | */ 17 | 18 | 19 | #ifndef DISPHELPER_H_INCLUDED 20 | #define DISPHELPER_H_INCLUDED 21 | 22 | #include 23 | #include 24 | 25 | #ifdef __cplusplus 26 | extern "C" { 27 | #endif 28 | 29 | HRESULT dhCreateObject(LPCOLESTR szProgId, LPCWSTR szMachine, IDispatch ** ppDisp); 30 | HRESULT dhGetObject(LPCOLESTR szFile, LPCOLESTR szProgId, IDispatch ** ppDisp); 31 | 32 | HRESULT dhCreateObjectEx(LPCOLESTR szProgId, REFIID riid, DWORD dwClsContext, COSERVERINFO * pServerInfo, void ** ppv); 33 | HRESULT dhGetObjectEx(LPCOLESTR szFile, LPCOLESTR szProgId, REFIID riid, DWORD dwClsContext, LPVOID lpvReserved, void ** ppv); 34 | 35 | HRESULT dhCallMethod(IDispatch * pDisp, LPCOLESTR szMember, ...); 36 | HRESULT dhPutValue(IDispatch * pDisp, LPCOLESTR szMember, ...); 37 | HRESULT dhPutRef(IDispatch * pDisp, LPCOLESTR szMember, ...); 38 | HRESULT dhGetValue(LPCWSTR szIdentifier, void * pResult, IDispatch * pDisp, LPCOLESTR szMember, ...); 39 | 40 | HRESULT dhInvoke(int invokeType, VARTYPE returnType, VARIANT * pvResult, IDispatch * pDisp, LPCOLESTR szMember, ...); 41 | HRESULT dhInvokeArray(int invokeType, VARIANT * pvResult, UINT cArgs, IDispatch * pDisp, LPCOLESTR szMember, VARIANT * pArgs); 42 | 43 | HRESULT dhCallMethodV(IDispatch * pDisp, LPCOLESTR szMember, va_list * marker); 44 | HRESULT dhPutValueV(IDispatch * pDisp, LPCOLESTR szMember, va_list * marker); 45 | HRESULT dhPutRefV(IDispatch * pDisp, LPCOLESTR szMember, va_list * marker); 46 | HRESULT dhGetValueV(LPCWSTR szIdentifier, void * pResult, IDispatch * pDisp, LPCOLESTR szMember, va_list * marker); 47 | HRESULT dhInvokeV(int invokeType, VARTYPE returnType, VARIANT * pvResult, IDispatch * pDisp, LPCOLESTR szMember, va_list * marker); 48 | 49 | HRESULT dhAutoWrap(int invokeType, VARIANT * pvResult, IDispatch * pDisp, LPCOLESTR szMember, UINT cArgs, ...); 50 | HRESULT dhParseProperties(IDispatch * pDisp, LPCWSTR szProperties, UINT * lpcPropsSet); 51 | 52 | HRESULT dhEnumBegin(IEnumVARIANT ** ppEnum, IDispatch * pDisp, LPCOLESTR szMember, ...); 53 | HRESULT dhEnumBeginV(IEnumVARIANT ** ppEnum, IDispatch * pDisp, LPCOLESTR szMember, va_list * marker); 54 | HRESULT dhEnumNextObject(IEnumVARIANT * pEnum, IDispatch ** ppDisp); 55 | HRESULT dhEnumNextVariant(IEnumVARIANT * pEnum, VARIANT * pvResult); 56 | 57 | HRESULT dhInitializeImp(BOOL bInitializeCOM, BOOL bUnicode); 58 | void dhUninitialize(BOOL bUninitializeCOM); 59 | 60 | #define dhInitializeA(bInitializeCOM) dhInitializeImp(bInitializeCOM, FALSE) 61 | #define dhInitializeW(bInitializeCOM) dhInitializeImp(bInitializeCOM, TRUE) 62 | 63 | #ifdef UNICODE 64 | #define dhInitialize dhInitializeW 65 | #else 66 | #define dhInitialize dhInitializeA 67 | #endif 68 | 69 | #define AutoWrap dhAutoWrap 70 | #define DISPATCH_OBJ(objName) IDispatch * objName = NULL 71 | #define dhFreeString(string) SysFreeString((BSTR) string) 72 | 73 | #ifndef SAFE_RELEASE 74 | #ifdef __cplusplus 75 | #define SAFE_RELEASE(pObj) { if (pObj) { (pObj)->Release(); (pObj) = NULL; } } 76 | #else 77 | #define SAFE_RELEASE(pObj) { if (pObj) { (pObj)->lpVtbl->Release(pObj); (pObj) = NULL; } } 78 | #endif 79 | #endif 80 | 81 | #define SAFE_FREE_STRING(string) { dhFreeString(string); (string) = NULL; } 82 | 83 | 84 | 85 | 86 | /* ===================================================================== */ 87 | #ifndef DISPHELPER_NO_WITH 88 | 89 | #define WITH0(objName, pDisp, szMember) { \ 90 | DISPATCH_OBJ(objName); \ 91 | if (SUCCEEDED(dhGetValue(L"%o", &objName, pDisp, szMember))) { 92 | 93 | #define WITH1(objName, pDisp, szMember, arg1) { \ 94 | DISPATCH_OBJ(objName); \ 95 | if (SUCCEEDED(dhGetValue(L"%o", &objName, pDisp, szMember, arg1))) { 96 | 97 | #define WITH2(objName, pDisp, szMember, arg1, arg2) { \ 98 | DISPATCH_OBJ(objName); \ 99 | if (SUCCEEDED(dhGetValue(L"%o", &objName, pDisp, szMember, arg1, arg2))) { 100 | 101 | #define WITH3(objName, pDisp, szMember, arg1, arg2, arg3) { \ 102 | DISPATCH_OBJ(objName); \ 103 | if (SUCCEEDED(dhGetValue(L"%o", &objName, pDisp, szMember, arg1, arg2, arg3))) { 104 | 105 | #define WITH4(objName, pDisp, szMember, arg1, arg2, arg3, arg4) { \ 106 | DISPATCH_OBJ(objName); \ 107 | if (SUCCEEDED(dhGetValue(L"%o", &objName, pDisp, szMember, arg1, arg2, arg3, arg4))) { 108 | 109 | #define WITH WITH0 110 | 111 | #define ON_WITH_ERROR(objName) } else { 112 | 113 | #define END_WITH(objName) } SAFE_RELEASE(objName); } 114 | 115 | #endif /* ----- DISPHELPER_NO_WITH ----- */ 116 | 117 | 118 | 119 | 120 | /* ===================================================================== */ 121 | #ifndef DISPHELPER_NO_FOR_EACH 122 | 123 | #define FOR_EACH0(objName, pDisp, szMember) { \ 124 | IEnumVARIANT * xx_pEnum_xx = NULL; \ 125 | DISPATCH_OBJ(objName); \ 126 | if (SUCCEEDED(dhEnumBegin(&xx_pEnum_xx, pDisp, szMember))) { \ 127 | while (dhEnumNextObject(xx_pEnum_xx, &objName) == NOERROR) { 128 | 129 | #define FOR_EACH1(objName, pDisp, szMember, arg1) { \ 130 | IEnumVARIANT * xx_pEnum_xx = NULL; \ 131 | DISPATCH_OBJ(objName); \ 132 | if (SUCCEEDED(dhEnumBegin(&xx_pEnum_xx, pDisp, szMember, arg1))) { \ 133 | while (dhEnumNextObject(xx_pEnum_xx, &objName) == NOERROR) { 134 | 135 | #define FOR_EACH2(objName, pDisp, szMember, arg1, arg2) { \ 136 | IEnumVARIANT * xx_pEnum_xx = NULL; \ 137 | DISPATCH_OBJ(objName); \ 138 | if (SUCCEEDED(dhEnumBegin(&xx_pEnum_xx, pDisp, szMember, arg1, arg2))) { \ 139 | while (dhEnumNextObject(xx_pEnum_xx, &objName) == NOERROR) { 140 | 141 | 142 | #define FOR_EACH3(objName, pDisp, szMember, arg1, arg2, arg3) { \ 143 | IEnumVARIANT * xx_pEnum_xx = NULL; \ 144 | DISPATCH_OBJ(objName); \ 145 | if (SUCCEEDED(dhEnumBegin(&xx_pEnum_xx, pDisp, szMember, arg1, arg2, arg3))) { \ 146 | while (dhEnumNextObject(xx_pEnum_xx, &objName) == NOERROR) { 147 | 148 | 149 | #define FOR_EACH4(objName, pDisp, szMember, arg1, arg2, arg3, arg4) { \ 150 | IEnumVARIANT * xx_pEnum_xx = NULL; \ 151 | DISPATCH_OBJ(objName); \ 152 | if (SUCCEEDED(dhEnumBegin(&xx_pEnum_xx, pDisp, szMember, arg1, arg2, arg3, arg4))) { \ 153 | while (dhEnumNextObject(xx_pEnum_xx, &objName) == NOERROR) { 154 | 155 | #define FOR_EACH FOR_EACH0 156 | 157 | #define ON_FOR_EACH_ERROR(objName) SAFE_RELEASE(objName); }} else {{ 158 | 159 | #define NEXT(objName) SAFE_RELEASE(objName); }} SAFE_RELEASE(objName); SAFE_RELEASE(xx_pEnum_xx); } 160 | 161 | #endif /* ----- DISPHELPER_NO_FOR_EACH ----- */ 162 | 163 | 164 | 165 | 166 | /* ===================================================================== */ 167 | #ifndef DISPHELPER_NO_EXCEPTIONS 168 | 169 | /* Structure to store a DispHelper exception */ 170 | typedef struct tagDH_EXCEPTION 171 | { 172 | LPCWSTR szInitialFunction; 173 | LPCWSTR szErrorFunction; 174 | 175 | HRESULT hr; 176 | 177 | WCHAR szMember[64]; 178 | WCHAR szCompleteMember[256]; 179 | 180 | UINT swCode; 181 | LPWSTR szDescription; 182 | LPWSTR szSource; 183 | LPWSTR szHelpFile; 184 | DWORD dwHelpContext; 185 | 186 | UINT iArgError; 187 | 188 | BOOL bDispatchError; 189 | 190 | #ifdef DISPHELPER_INTERNAL_BUILD 191 | BOOL bOld; 192 | #endif 193 | } DH_EXCEPTION, * PDH_EXCEPTION; 194 | 195 | typedef void (*DH_EXCEPTION_CALLBACK) (PDH_EXCEPTION); 196 | 197 | /* Structure to store exception options. */ 198 | typedef struct tagDH_EXCEPTION_OPTIONS 199 | { 200 | HWND hwnd; 201 | LPCWSTR szAppName; 202 | BOOL bShowExceptions; 203 | BOOL bDisableRecordExceptions; 204 | DH_EXCEPTION_CALLBACK pfnExceptionCallback; 205 | } DH_EXCEPTION_OPTIONS, * PDH_EXCEPTION_OPTIONS; 206 | 207 | /* Functions to manipulate global exception options */ 208 | HRESULT dhToggleExceptions(BOOL bShow); 209 | HRESULT dhSetExceptionOptions(PDH_EXCEPTION_OPTIONS pExceptionOptions); 210 | HRESULT dhGetExceptionOptions(PDH_EXCEPTION_OPTIONS pExceptionOptions); 211 | 212 | /* Functions to show an exception, format an exception into a string 213 | * and get a copy of the last exception */ 214 | HRESULT dhShowException(PDH_EXCEPTION pException); 215 | HRESULT dhGetLastException(PDH_EXCEPTION * pException); 216 | HRESULT dhFormatExceptionW(PDH_EXCEPTION pException, LPWSTR szBuffer, UINT cchBufferSize, BOOL bFixedFont); 217 | HRESULT dhFormatExceptionA(PDH_EXCEPTION pException, LPSTR szBuffer, UINT cchBufferSize, BOOL bFixedFont); 218 | 219 | #ifdef UNICODE 220 | #define dhFormatException dhFormatExceptionW 221 | #else 222 | #define dhFormatException dhFormatExceptionA 223 | #endif 224 | 225 | #ifdef DISPHELPER_INTERNAL_BUILD 226 | 227 | void dhEnter(void); 228 | HRESULT dhExitEx(HRESULT hr, BOOL bDispatchError, LPCWSTR szMember, LPCWSTR szCompleteMember, EXCEPINFO * pExcepInfo, UINT iArgError, LPCWSTR szFunctionName); 229 | void dhCleanupThreadException(void); 230 | 231 | #define DH_ENTER(szFunctionName) static LPCWSTR xx_szFunctionName_xx = szFunctionName; \ 232 | dhEnter() 233 | 234 | #define DH_EXITEX(hr, bDispatchError, szMember, szCompleteMember, pExcepInfo, iArgError) \ 235 | dhExitEx(hr, bDispatchError, szMember, szCompleteMember, pExcepInfo, iArgError, xx_szFunctionName_xx) 236 | 237 | #define DH_EXIT(hr, szCompleteMember) DH_EXITEX(hr, FALSE, NULL, szCompleteMember, NULL, 0) 238 | 239 | #endif /* ----- DISPHELPER_INTERNAL_BUILD ----- */ 240 | 241 | #else /* ----- DISPHELPER_NO_EXCEPTIONS ----- */ 242 | 243 | /* These macros define out calls to selected exception functions */ 244 | #define dhToggleExceptions(bShow) (E_NOTIMPL) 245 | #define dhSetExceptionOptions(pExcepOptions) (E_NOTIMPL) 246 | 247 | #ifdef DISPHELPER_INTERNAL_BUILD 248 | #define DH_ENTER(szFunctionName) 249 | #define DH_EXITEX(hr, bDispatchError, szMember, szCompleteMember, pExcepInfo, iArgError) \ 250 | (((hr == DISP_E_EXCEPTION && pExcepInfo) ? \ 251 | (SysFreeString(((EXCEPINFO *)(pExcepInfo))->bstrSource), \ 252 | SysFreeString(((EXCEPINFO *)(pExcepInfo))->bstrDescription), \ 253 | SysFreeString(((EXCEPINFO *)(pExcepInfo))->bstrHelpFile), 0) : (0)), hr) 254 | #define DH_EXIT(hr, szCompleteMember)(hr) 255 | #endif 256 | 257 | #endif /* ----- DISPHELPER_NO_EXCEPTIONS ----- */ 258 | 259 | 260 | 261 | 262 | 263 | 264 | /* ===================================================================== */ 265 | #ifdef DISPHELPER_INTERNAL_BUILD 266 | 267 | #include 268 | #include 269 | #include 270 | 271 | /* Macro to include or lose debug code. */ 272 | #ifdef DEBUG 273 | #define DBG_CODE(code) code 274 | #else 275 | #define DBG_CODE(code) 276 | #endif 277 | 278 | /* Are we in unicode mode? */ 279 | extern BOOL dh_g_bIsUnicodeMode; 280 | 281 | /* Number of objects in an array */ 282 | #undef ARRAYSIZE 283 | #define ARRAYSIZE(arr) (sizeof(arr) / sizeof((arr)[0])) 284 | 285 | /* Maximum number of arguments for a member */ 286 | #define DH_MAX_ARGS 25 287 | 288 | /* Maximum length of a member string */ 289 | #define DH_MAX_MEMBER 512 290 | 291 | /* This macro is missing from Dev-Cpp/Mingw */ 292 | #ifndef V_UI4 293 | #define V_UI4(X) V_UNION(X, ulVal) 294 | #endif 295 | 296 | /* Macro to notify programmer of invalid identifier in debug mode. */ 297 | #define DEBUG_NOTIFY_INVALID_IDENTIFIER(chIdentifier) \ 298 | DBG_CODE( { \ 299 | char buf[256]; \ 300 | sprintf(buf,"DEBUG: The format string or return identifier contained the invalid identifier '%c'.\n" \ 301 | "The valid identifiers are \"d/u/e/b/v/B/S/s/T/o/O/t/W/D/f/m\".\n" \ 302 | "Each %% character should be followed by a valid identifier.\n" \ 303 | "Identifiers are case sensitive.", (chIdentifier)); \ 304 | MessageBoxA(NULL, buf, "DEBUG: Invalid Format Identifier", MB_ICONSTOP); \ 305 | } ) 306 | 307 | #ifdef _MSC_VER 308 | #pragma warning(disable : 4706) /* Assignment in conditional expression */ 309 | #endif 310 | 311 | #ifndef DISPHELPER_NO_PRAGMA_LIB 312 | #ifdef __LCC__ 313 | #pragma lib 314 | #pragma lib 315 | #pragma lib 316 | #endif 317 | #endif 318 | 319 | 320 | #endif /* ----- DISPHELPER_INTERNAL_BUILD ----- */ 321 | 322 | #ifndef DISPHELPER_NO_PRAGMA_LIB 323 | #if defined(_MSC_VER) || defined(__BORLANDC__) 324 | #pragma comment(lib, "ole32.lib") 325 | #pragma comment(lib, "oleaut32.lib") 326 | #pragma comment(lib, "uuid.lib") 327 | #endif 328 | #endif 329 | 330 | 331 | 332 | #ifdef __cplusplus 333 | } 334 | #endif 335 | 336 | 337 | 338 | 339 | /* ===================================================================== */ 340 | #if defined(__cplusplus) && !defined(DISPHELPER_NO_CPP_EXTENSIONS) 341 | 342 | #include 343 | #include 344 | 345 | #ifdef _MSC_VER 346 | #pragma warning( disable : 4290 ) /* throw() specification ignored */ 347 | #endif 348 | 349 | #ifndef DISPHELPER_USE_MS_SMART_PTR 350 | 351 | template 352 | class CDhComPtr 353 | { 354 | public: 355 | CDhComPtr() throw() : m_pInterface (NULL) {} 356 | 357 | CDhComPtr(T* pInterface) throw() : m_pInterface (pInterface) 358 | { 359 | if (m_pInterface) m_pInterface->AddRef(); 360 | } 361 | 362 | CDhComPtr(const CDhComPtr& original) throw() : m_pInterface (original.m_pInterface) 363 | { 364 | if (m_pInterface) m_pInterface->AddRef(); 365 | } 366 | 367 | ~CDhComPtr() throw() 368 | { 369 | Dispose(); 370 | } 371 | 372 | void Dispose() throw() 373 | { 374 | if (m_pInterface) 375 | { 376 | m_pInterface->Release(); 377 | m_pInterface = NULL; 378 | } 379 | } 380 | 381 | T* Detach() throw() 382 | { 383 | T* temp = m_pInterface; 384 | m_pInterface = NULL; 385 | return temp; 386 | } 387 | 388 | inline operator T*() const throw() 389 | { 390 | return m_pInterface; 391 | } 392 | 393 | T** operator&() throw() 394 | { 395 | Dispose(); 396 | return &m_pInterface; 397 | } 398 | 399 | T* operator->() const throw(HRESULT) 400 | { 401 | if (!m_pInterface) throw E_POINTER; 402 | return m_pInterface; 403 | } 404 | 405 | CDhComPtr& operator=(T* pInterface) throw() 406 | { 407 | if (m_pInterface != pInterface) 408 | { 409 | T* pOldInterface = m_pInterface; 410 | m_pInterface = pInterface; 411 | if (m_pInterface) m_pInterface->AddRef(); 412 | if (pOldInterface) pOldInterface->Release(); 413 | } 414 | 415 | return *this; 416 | } 417 | 418 | CDhComPtr& operator=(const int null) throw(HRESULT) 419 | { 420 | if (null != 0) throw(E_POINTER); 421 | return operator=((T*) NULL); 422 | } 423 | 424 | CDhComPtr& operator=(const CDhComPtr& rhs) throw() 425 | { 426 | return operator=(rhs.m_pInterface); 427 | } 428 | 429 | private: 430 | T* m_pInterface; 431 | }; 432 | 433 | typedef CDhComPtr CDispPtr; 434 | typedef CDhComPtr CEnumPtr; 435 | typedef CDhComPtr CUnknownPtr; 436 | 437 | #else /* DISPHELPER_USE_MS_SMART_PTR */ 438 | 439 | #include 440 | typedef IDispatchPtr CDispPtr; 441 | typedef IEnumVARIANTPtr CEnumPtr; 442 | typedef IUnknownPtr CUnknownPtr; 443 | 444 | #endif /* DISPHELPER_USE_MS_SMART_PTR */ 445 | 446 | 447 | 448 | 449 | /* ===================================================================== */ 450 | template 451 | class CDhStringTemplate 452 | { 453 | public: 454 | CDhStringTemplate() throw() : m_strptr (NULL) {} 455 | 456 | CDhStringTemplate(const CDhStringTemplate& original) throw() 457 | { 458 | Copy(original.m_strptr); 459 | } 460 | 461 | CDhStringTemplate(const int null) throw(HRESULT) : m_strptr (NULL) 462 | { 463 | if (null != 0) throw(E_POINTER); 464 | } 465 | 466 | ~CDhStringTemplate() throw() 467 | { 468 | Dispose(); 469 | } 470 | 471 | void Dispose() throw() 472 | { 473 | dhFreeString(m_strptr); 474 | m_strptr = NULL; 475 | } 476 | 477 | T* Detach() throw() 478 | { 479 | T* temp = m_strptr; 480 | m_strptr = NULL; 481 | return temp; 482 | } 483 | 484 | T** operator&() throw() 485 | { 486 | Dispose(); 487 | return &m_strptr; 488 | } 489 | 490 | inline operator T*() const throw() 491 | { 492 | return m_strptr; 493 | } 494 | 495 | inline T& operator[](int nIndex) const throw() 496 | { 497 | return m_strptr[nIndex]; 498 | } 499 | 500 | CDhStringTemplate& operator=(const CDhStringTemplate& rhs) 501 | { 502 | if (m_strptr != rhs.m_strptr) 503 | { 504 | T* temp = m_strptr; 505 | Copy(rhs.m_strptr); 506 | dhFreeString(temp); 507 | } 508 | 509 | return *this; 510 | } 511 | 512 | CDhStringTemplate& operator=(const int null) throw(HRESULT) 513 | { 514 | if (null != 0) throw(E_POINTER); 515 | Dispose(); 516 | return *this; 517 | } 518 | 519 | private: 520 | void Copy(const T* rhs) 521 | { 522 | if (rhs == NULL) 523 | { 524 | m_strptr = NULL; 525 | } 526 | else if (sizeof(T) == sizeof(CHAR)) 527 | { 528 | m_strptr = (T*) SysAllocStringByteLen((LPCSTR) rhs, SysStringByteLen((BSTR) rhs)); 529 | } 530 | else 531 | { 532 | m_strptr = (T*) SysAllocStringLen((OLECHAR *) rhs, SysStringLen((BSTR) rhs)); 533 | } 534 | } 535 | 536 | T* m_strptr; 537 | }; 538 | 539 | typedef CDhStringTemplate CDhStringA; /* Ansi string - LPSTR */ 540 | typedef CDhStringTemplate CDhStringW; /* Unicode string - LPWSTR */ 541 | typedef CDhStringTemplate CDhStringB; /* Unicode bstring - BSTR */ 542 | typedef CDhStringTemplate CDhStringT; /* T string - LPTSTR */ 543 | typedef CDhStringTemplate CDhString; /* T string - LPTSTR */ 544 | 545 | inline std::ostream& operator<<(std::ostream& os, const CDhStringA& s) 546 | { 547 | return os << (s ? s : (char*) "(null)"); 548 | } 549 | 550 | inline std::wostream& operator<<(std::wostream& os, const CDhStringW& s) 551 | { 552 | return os << (s ? s : (wchar_t*) L"(null)"); 553 | } 554 | 555 | 556 | 557 | 558 | /* ===================================================================== */ 559 | class CDhInitialize 560 | { 561 | public: 562 | CDhInitialize(const BOOL bInitCom = TRUE) throw() : m_bInitCom (bInitCom) 563 | { 564 | dhInitialize(m_bInitCom); 565 | } 566 | 567 | ~CDhInitialize() throw() 568 | { 569 | dhUninitialize(m_bInitCom); 570 | } 571 | private: 572 | BOOL m_bInitCom; 573 | }; 574 | 575 | 576 | 577 | 578 | /* ===================================================================== */ 579 | #ifndef DISPHELPER_NO_EXCEPTIONS 580 | class dhThrowFunctions 581 | { 582 | public: 583 | static void throw_string() throw(std::string) 584 | { 585 | CHAR szMessage[512]; 586 | dhFormatExceptionA(NULL, szMessage, sizeof(szMessage)/sizeof(szMessage[0]), TRUE); 587 | throw std::string(szMessage); 588 | } 589 | 590 | static void throw_wstring() throw(std::wstring) 591 | { 592 | WCHAR szMessage[512]; 593 | dhFormatExceptionW(NULL, szMessage, sizeof(szMessage)/sizeof(szMessage[0]), TRUE); 594 | throw std::wstring(szMessage); 595 | } 596 | 597 | static void throw_dhexception() throw(PDH_EXCEPTION) 598 | { 599 | PDH_EXCEPTION pException = NULL; 600 | dhGetLastException(&pException); 601 | throw pException; 602 | } 603 | }; 604 | #endif /* DISPHELPER_NO_EXCEPTIONS */ 605 | 606 | 607 | 608 | 609 | /* ===================================================================== */ 610 | #ifndef DISPHELPER_NO_EXCEPTIONS 611 | inline bool dhIfFailThrowString(HRESULT hr) throw(std::string) 612 | { 613 | if (FAILED(hr)) dhThrowFunctions::throw_string(); 614 | return true; 615 | } 616 | 617 | inline bool dhIfFailThrowWString(HRESULT hr) throw(std::wstring) 618 | { 619 | if (FAILED(hr)) dhThrowFunctions::throw_wstring(); 620 | return true; 621 | } 622 | 623 | inline bool dhIfFailThrowDhException(HRESULT hr) throw(PDH_EXCEPTION) 624 | { 625 | if (FAILED(hr)) dhThrowFunctions::throw_dhexception(); 626 | return true; 627 | } 628 | 629 | #define dhCheck dhIfFailThrowString 630 | 631 | #endif /* DISPHELPER_NO_EXCEPTIONS */ 632 | 633 | 634 | 635 | 636 | /* ===================================================================== */ 637 | #ifndef DISPHELPER_NO_WITH 638 | 639 | #undef WITH0 640 | #define WITH0(objName, pDisp, szMember) { \ 641 | CDispPtr objName; \ 642 | if (SUCCEEDED(dhGetValue(L"%o", &objName, pDisp, szMember))) { 643 | 644 | #undef WITH1 645 | #define WITH1(objName, pDisp, szMember, arg1) { \ 646 | CDispPtr objName; \ 647 | if (SUCCEEDED(dhGetValue(L"%o", &objName, pDisp, szMember, arg1))) { 648 | 649 | #undef WITH2 650 | #define WITH2(objName, pDisp, szMember, arg1, arg2) { \ 651 | CDispPtr objName; \ 652 | if (SUCCEEDED(dhGetValue(L"%o", &objName, pDisp, szMember, arg1, arg2))) { 653 | 654 | #undef WITH3 655 | #define WITH3(objName, pDisp, szMember, arg1, arg2, arg3) { \ 656 | CDispPtr objName; \ 657 | if (SUCCEEDED(dhGetValue(L"%o", &objName, pDisp, szMember, arg1, arg2, arg3))) { 658 | 659 | #undef WITH4 660 | #define WITH4(objName, pDisp, szMember, arg1, arg2, arg3, arg4) { \ 661 | CDispPtr objName; \ 662 | if (SUCCEEDED(dhGetValue(L"%o", &objName, pDisp, szMember, arg1, arg2, arg3, arg4))) { 663 | 664 | #undef ON_WITH_ERROR 665 | #define ON_WITH_ERROR(objName) } else { 666 | 667 | #undef END_WITH 668 | #define END_WITH(objName) }} 669 | 670 | #define END_WITH_THROW(objName) } else { dhThrowFunctions::throw_string(); }} 671 | 672 | #endif /* DISPHELPER_NO_WITH */ 673 | 674 | 675 | 676 | 677 | /* ===================================================================== */ 678 | #ifndef DISPHELPER_NO_FOR_EACH 679 | 680 | #undef FOR_EACH0 681 | #define FOR_EACH0(objName, pDisp, szMember) { \ 682 | CEnumPtr xx_pEnum_xx; \ 683 | if (SUCCEEDED(dhEnumBegin(&xx_pEnum_xx, pDisp, szMember))) { \ 684 | CDispPtr objName; \ 685 | while (dhEnumNextObject(xx_pEnum_xx, &objName) == NOERROR) { 686 | 687 | #undef FOR_EACH1 688 | #define FOR_EACH1(objName, pDisp, szMember, arg1) { \ 689 | CEnumPtr xx_pEnum_xx; \ 690 | if (SUCCEEDED(dhEnumBegin(&xx_pEnum_xx, pDisp, szMember, arg1))) { \ 691 | CDispPtr objName; \ 692 | while (dhEnumNextObject(xx_pEnum_xx, &objName) == NOERROR) { 693 | 694 | #undef FOR_EACH2 695 | #define FOR_EACH2(objName, pDisp, szMember, arg1, arg2) { \ 696 | CEnumPtr xx_pEnum_xx; \ 697 | if (SUCCEEDED(dhEnumBegin(&xx_pEnum_xx, pDisp, szMember, arg1, arg2))) { \ 698 | CDispPtr objName; \ 699 | while (dhEnumNextObject(xx_pEnum_xx, &objName) == NOERROR) { 700 | 701 | #undef FOR_EACH3 702 | #define FOR_EACH3(objName, pDisp, szMember, arg1, arg2, arg3) { \ 703 | CEnumPtr xx_pEnum_xx; \ 704 | if (SUCCEEDED(dhEnumBegin(&xx_pEnum_xx, pDisp, szMember, arg1, arg2, arg3))) { \ 705 | CDispPtr objName; \ 706 | while (dhEnumNextObject(xx_pEnum_xx, &objName) == NOERROR) { 707 | 708 | #undef FOR_EACH4 709 | #define FOR_EACH4(objName, pDisp, szMember, arg1, arg2, arg3, arg4) { \ 710 | CEnumPtr xx_pEnum_xx; \ 711 | if (SUCCEEDED(dhEnumBegin(&xx_pEnum_xx, pDisp, szMember, arg1, arg2, arg3, arg4))) { \ 712 | CDispPtr objName; \ 713 | while (dhEnumNextObject(xx_pEnum_xx, &objName) == NOERROR) { 714 | 715 | #undef ON_FOR_EACH_ERROR 716 | #define ON_FOR_EACH_ERROR(objName) }} else {{ 717 | 718 | #undef NEXT 719 | #define NEXT(objName) }}} 720 | 721 | #define NEXT_THROW(objName) }} else { dhThrowFunctions::throw_string(); }} 722 | 723 | #endif /* DISPHELPER_NO_FOR_EACH */ 724 | 725 | #ifdef _MSC_VER 726 | #pragma warning( default : 4290 ) 727 | #endif 728 | 729 | #endif /* defined(__cplusplus) && !defined(DISPHELPER_NO_CPP_EXTENSIONS) */ 730 | 731 | #endif /* ----- DISPHELPER_H_INCLUDED ----- */ 732 | -------------------------------------------------------------------------------- /src/serialport.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "serialport.h" 4 | 5 | #ifdef WIN32 6 | #define strncasecmp strnicmp 7 | #else 8 | #include "serialport_poller.h" 9 | #endif 10 | 11 | struct _WriteQueue { 12 | const int _fd; // the fd that is associated with this write queue 13 | QueuedWrite _write_queue; 14 | uv_mutex_t _write_queue_mutex; 15 | _WriteQueue *_next; 16 | 17 | _WriteQueue(const int fd) : _fd(fd), _write_queue(), _next(NULL) { 18 | uv_mutex_init(&_write_queue_mutex); 19 | } 20 | 21 | void lock() { uv_mutex_lock(&_write_queue_mutex); }; 22 | void unlock() { uv_mutex_unlock(&_write_queue_mutex); }; 23 | 24 | QueuedWrite &get() { return _write_queue; } 25 | }; 26 | 27 | 28 | static _WriteQueue *write_queues = NULL; 29 | 30 | static _WriteQueue *qForFD(const int fd) { 31 | _WriteQueue *q = write_queues; 32 | while (q != NULL) { 33 | if (q->_fd == fd) { 34 | return q; 35 | } 36 | q = q->_next; 37 | } 38 | return NULL; 39 | }; 40 | 41 | static _WriteQueue *newQForFD(const int fd) { 42 | _WriteQueue *q = qForFD(fd); 43 | 44 | if (q == NULL) { 45 | if (write_queues == NULL) { 46 | write_queues = new _WriteQueue(fd); 47 | return write_queues; 48 | } else { 49 | q = write_queues; 50 | while (q->_next != NULL) { 51 | q = q->_next; 52 | } 53 | q->_next = new _WriteQueue(fd); 54 | return q->_next; 55 | } 56 | } 57 | 58 | return q; 59 | }; 60 | 61 | static void deleteQForFD(const int fd) { 62 | if (write_queues == NULL) 63 | return; 64 | 65 | _WriteQueue *q = write_queues; 66 | if (write_queues->_fd == fd) { 67 | write_queues = write_queues->_next; 68 | delete q; 69 | 70 | return; 71 | } 72 | 73 | while (q->_next != NULL) { 74 | if (q->_next->_fd == fd) { 75 | _WriteQueue *out_q = q->_next; 76 | q->_next = q->_next->_next; 77 | delete out_q; 78 | 79 | return; 80 | } 81 | q = q->_next; 82 | } 83 | 84 | // It wasn't found... 85 | }; 86 | 87 | 88 | 89 | NAN_METHOD(Open) { 90 | 91 | // path 92 | if(!info[0]->IsString()) { 93 | Nan::ThrowTypeError("First argument must be a string"); 94 | return; 95 | } 96 | v8::String::Utf8Value path(info[0]->ToString()); 97 | 98 | // options 99 | if(!info[1]->IsObject()) { 100 | Nan::ThrowTypeError("Second argument must be an object"); 101 | return; 102 | } 103 | v8::Local options = info[1]->ToObject(); 104 | 105 | // callback 106 | if(!info[2]->IsFunction()) { 107 | Nan::ThrowTypeError("Third argument must be a function"); 108 | return; 109 | } 110 | v8::Local callback = info[2].As(); 111 | 112 | OpenBaton* baton = new OpenBaton(); 113 | memset(baton, 0, sizeof(OpenBaton)); 114 | strcpy(baton->path, *path); 115 | baton->baudRate = Nan::Get(options, Nan::New("baudRate").ToLocalChecked()).ToLocalChecked()->ToInt32()->Int32Value(); 116 | baton->dataBits = Nan::Get(options, Nan::New("dataBits").ToLocalChecked()).ToLocalChecked()->ToInt32()->Int32Value(); 117 | baton->bufferSize = Nan::Get(options, Nan::New("bufferSize").ToLocalChecked()).ToLocalChecked()->ToInt32()->Int32Value(); 118 | baton->parity = ToParityEnum(Nan::Get(options, Nan::New("parity").ToLocalChecked()).ToLocalChecked()->ToString()); 119 | baton->stopBits = ToStopBitEnum(Nan::Get(options, Nan::New("stopBits").ToLocalChecked()).ToLocalChecked()->ToNumber()->NumberValue()); 120 | baton->rtscts = Nan::Get(options, Nan::New("rtscts").ToLocalChecked()).ToLocalChecked()->ToBoolean()->BooleanValue(); 121 | baton->xon = Nan::Get(options, Nan::New("xon").ToLocalChecked()).ToLocalChecked()->ToBoolean()->BooleanValue(); 122 | baton->xoff = Nan::Get(options, Nan::New("xoff").ToLocalChecked()).ToLocalChecked()->ToBoolean()->BooleanValue(); 123 | baton->xany = Nan::Get(options, Nan::New("xany").ToLocalChecked()).ToLocalChecked()->ToBoolean()->BooleanValue(); 124 | baton->hupcl = Nan::Get(options, Nan::New("hupcl").ToLocalChecked()).ToLocalChecked()->ToBoolean()->BooleanValue(); 125 | 126 | v8::Local platformOptions = Nan::Get(options, Nan::New("platformOptions").ToLocalChecked()).ToLocalChecked()->ToObject(); 127 | baton->platformOptions = ParsePlatformOptions(platformOptions); 128 | 129 | baton->callback = new Nan::Callback(callback); 130 | baton->dataCallback = new Nan::Callback(Nan::Get(options, Nan::New("dataCallback").ToLocalChecked()).ToLocalChecked().As()); 131 | baton->disconnectedCallback = new Nan::Callback(Nan::Get(options, Nan::New("disconnectedCallback").ToLocalChecked()).ToLocalChecked().As()); 132 | baton->errorCallback = new Nan::Callback(Nan::Get(options, Nan::New("errorCallback").ToLocalChecked()).ToLocalChecked().As()); 133 | 134 | uv_work_t* req = new uv_work_t(); 135 | req->data = baton; 136 | 137 | uv_queue_work(uv_default_loop(), req, EIO_Open, (uv_after_work_cb)EIO_AfterOpen); 138 | 139 | return; 140 | } 141 | 142 | void EIO_AfterOpen(uv_work_t* req) { 143 | Nan::HandleScope scope; 144 | 145 | OpenBaton* data = static_cast(req->data); 146 | 147 | v8::Local argv[2]; 148 | if(data->errorString[0]) { 149 | argv[0] = v8::Exception::Error(Nan::New(data->errorString).ToLocalChecked()); 150 | argv[1] = Nan::Undefined(); 151 | // not needed for AfterOpenSuccess 152 | delete data->dataCallback; 153 | delete data->errorCallback; 154 | delete data->disconnectedCallback; 155 | } else { 156 | argv[0] = Nan::Undefined(); 157 | argv[1] = Nan::New(data->result); 158 | 159 | int fd = argv[1]->ToInt32()->Int32Value(); 160 | newQForFD(fd); 161 | 162 | AfterOpenSuccess(data->result, data->dataCallback, data->disconnectedCallback, data->errorCallback); 163 | } 164 | 165 | data->callback->Call(2, argv); 166 | 167 | delete data->platformOptions; 168 | delete data->callback; 169 | delete data; 170 | delete req; 171 | } 172 | 173 | NAN_METHOD(Update) { 174 | 175 | // file descriptor 176 | if(!info[0]->IsInt32()) { 177 | Nan::ThrowTypeError("First argument must be an int"); 178 | return; 179 | } 180 | int fd = info[0]->ToInt32()->Int32Value(); 181 | 182 | // options 183 | if(!info[1]->IsObject()) { 184 | Nan::ThrowTypeError("Second argument must be an object"); 185 | return; 186 | } 187 | v8::Local options = info[1]->ToObject(); 188 | 189 | // callback 190 | if(!info[2]->IsFunction()) { 191 | Nan::ThrowTypeError("Third argument must be a function"); 192 | return; 193 | } 194 | v8::Local callback = info[2].As(); 195 | 196 | OpenBaton* baton = new OpenBaton(); 197 | memset(baton, 0, sizeof(OpenBaton)); 198 | baton->fd = fd; 199 | baton->baudRate = Nan::Get(options, Nan::New("baudRate").ToLocalChecked()).ToLocalChecked()->ToInt32()->Int32Value(); 200 | baton->dataBits = Nan::Get(options, Nan::New("dataBits").ToLocalChecked()).ToLocalChecked()->ToInt32()->Int32Value(); 201 | baton->bufferSize = Nan::Get(options, Nan::New("bufferSize").ToLocalChecked()).ToLocalChecked()->ToInt32()->Int32Value(); 202 | baton->parity = ToParityEnum(Nan::Get(options, Nan::New("parity").ToLocalChecked()).ToLocalChecked()->ToString()); 203 | baton->stopBits = ToStopBitEnum(Nan::Get(options, Nan::New("stopBits").ToLocalChecked()).ToLocalChecked()->ToNumber()->NumberValue()); 204 | baton->rtscts = Nan::Get(options, Nan::New("rtscts").ToLocalChecked()).ToLocalChecked()->ToBoolean()->BooleanValue(); 205 | baton->xon = Nan::Get(options, Nan::New("xon").ToLocalChecked()).ToLocalChecked()->ToBoolean()->BooleanValue(); 206 | baton->xoff = Nan::Get(options, Nan::New("xoff").ToLocalChecked()).ToLocalChecked()->ToBoolean()->BooleanValue(); 207 | baton->xany = Nan::Get(options, Nan::New("xany").ToLocalChecked()).ToLocalChecked()->ToBoolean()->BooleanValue(); 208 | 209 | v8::Local platformOptions = Nan::Get(options, Nan::New("platformOptions").ToLocalChecked()).ToLocalChecked()->ToObject(); 210 | baton->platformOptions = ParsePlatformOptions(platformOptions); 211 | 212 | baton->callback = new Nan::Callback(callback); 213 | baton->dataCallback = new Nan::Callback(Nan::Get(options, Nan::New("dataCallback").ToLocalChecked()).ToLocalChecked().As()); 214 | baton->disconnectedCallback = new Nan::Callback(Nan::Get(options, Nan::New("disconnectedCallback").ToLocalChecked()).ToLocalChecked().As()); 215 | baton->errorCallback = new Nan::Callback(Nan::Get(options, Nan::New("errorCallback").ToLocalChecked()).ToLocalChecked().As()); 216 | 217 | uv_work_t* req = new uv_work_t(); 218 | req->data = baton; 219 | 220 | uv_queue_work(uv_default_loop(), req, EIO_Update, (uv_after_work_cb)EIO_AfterUpdate); 221 | 222 | return; 223 | } 224 | 225 | void EIO_AfterUpdate(uv_work_t* req) { 226 | Nan::HandleScope scope; 227 | 228 | OpenBaton* data = static_cast(req->data); 229 | 230 | v8::Local argv[2]; 231 | if(data->errorString[0]) { 232 | argv[0] = v8::Exception::Error(Nan::New(data->errorString).ToLocalChecked()); 233 | argv[1] = Nan::Undefined(); 234 | // not needed for AfterOpenSuccess 235 | delete data->dataCallback; 236 | delete data->errorCallback; 237 | delete data->disconnectedCallback; 238 | } else { 239 | argv[0] = Nan::Undefined(); 240 | argv[1] = Nan::New(data->result); 241 | 242 | int fd = argv[1]->ToInt32()->Int32Value(); 243 | newQForFD(fd); 244 | 245 | AfterOpenSuccess(data->result, data->dataCallback, data->disconnectedCallback, data->errorCallback); 246 | } 247 | 248 | data->callback->Call(2, argv); 249 | 250 | delete data->platformOptions; 251 | delete data->callback; 252 | delete data; 253 | delete req; 254 | } 255 | 256 | NAN_METHOD(Write) { 257 | 258 | // file descriptor 259 | if(!info[0]->IsInt32()) { 260 | Nan::ThrowTypeError("First argument must be an int"); 261 | return; 262 | } 263 | int fd = info[0]->ToInt32()->Int32Value(); 264 | 265 | // buffer 266 | if(!info[1]->IsObject() || !node::Buffer::HasInstance(info[1])) { 267 | Nan::ThrowTypeError("Second argument must be a buffer"); 268 | return; 269 | } 270 | v8::Local buffer = info[1]->ToObject(); 271 | char* bufferData = node::Buffer::Data(buffer); 272 | size_t bufferLength = node::Buffer::Length(buffer); 273 | 274 | // callback 275 | if(!info[2]->IsFunction()) { 276 | Nan::ThrowTypeError("Third argument must be a function"); 277 | return; 278 | } 279 | v8::Local callback = info[2].As(); 280 | 281 | WriteBaton* baton = new WriteBaton(); 282 | memset(baton, 0, sizeof(WriteBaton)); 283 | baton->fd = fd; 284 | baton->buffer.Reset(buffer); 285 | baton->bufferData = bufferData; 286 | baton->bufferLength = bufferLength; 287 | baton->offset = 0; 288 | baton->callback = new Nan::Callback(callback); 289 | 290 | QueuedWrite* queuedWrite = new QueuedWrite(); 291 | memset(queuedWrite, 0, sizeof(QueuedWrite)); 292 | queuedWrite->baton = baton; 293 | queuedWrite->req.data = queuedWrite; 294 | 295 | _WriteQueue *q = qForFD(fd); 296 | if(!q) { 297 | Nan::ThrowTypeError("There's no write queue for that file descriptor (write)!"); 298 | return; 299 | } 300 | 301 | q->lock(); 302 | QueuedWrite &write_queue = q->get(); 303 | bool empty = write_queue.empty(); 304 | 305 | write_queue.insert_tail(queuedWrite); 306 | 307 | if (empty) { 308 | uv_queue_work(uv_default_loop(), &queuedWrite->req, EIO_Write, (uv_after_work_cb)EIO_AfterWrite); 309 | } 310 | q->unlock(); 311 | 312 | return; 313 | } 314 | 315 | void EIO_AfterWrite(uv_work_t* req) { 316 | Nan::HandleScope scope; 317 | 318 | QueuedWrite* queuedWrite = static_cast(req->data); 319 | WriteBaton* data = static_cast(queuedWrite->baton); 320 | 321 | v8::Local argv[2]; 322 | if(data->errorString[0]) { 323 | argv[0] = v8::Exception::Error(Nan::New(data->errorString).ToLocalChecked()); 324 | argv[1] = Nan::Undefined(); 325 | } else { 326 | argv[0] = Nan::Undefined(); 327 | argv[1] = Nan::New(data->result); 328 | } 329 | data->callback->Call(2, argv); 330 | 331 | if (data->offset < data->bufferLength && !data->errorString[0]) { 332 | // We're not done with this baton, so throw it right back onto the queue. 333 | // Don't re-push the write in the event loop if there was an error; because same error could occur again! 334 | // TODO: Add a uv_poll here for unix... 335 | //fprintf(stderr, "Write again...\n"); 336 | uv_queue_work(uv_default_loop(), req, EIO_Write, (uv_after_work_cb)EIO_AfterWrite); 337 | return; 338 | } 339 | 340 | int fd = data->fd; 341 | _WriteQueue *q = qForFD(fd); 342 | if(!q) { 343 | Nan::ThrowTypeError("There's no write queue for that file descriptor (after write)!"); 344 | return; 345 | } 346 | 347 | q->lock(); 348 | QueuedWrite &write_queue = q->get(); 349 | 350 | // remove this one from the list 351 | queuedWrite->remove(); 352 | 353 | // If there are any left, start a new thread to write the next one. 354 | if (!write_queue.empty()) { 355 | // Always pull the next work item from the head of the queue 356 | QueuedWrite* nextQueuedWrite = write_queue.next; 357 | uv_queue_work(uv_default_loop(), &nextQueuedWrite->req, EIO_Write, (uv_after_work_cb)EIO_AfterWrite); 358 | } 359 | q->unlock(); 360 | 361 | data->buffer.Reset(); 362 | delete data->callback; 363 | delete data; 364 | delete queuedWrite; 365 | } 366 | 367 | NAN_METHOD(Close) { 368 | 369 | // file descriptor 370 | if(!info[0]->IsInt32()) { 371 | Nan::ThrowTypeError("First argument must be an int"); 372 | return; 373 | } 374 | int fd = info[0]->ToInt32()->Int32Value(); 375 | 376 | // callback 377 | if(!info[1]->IsFunction()) { 378 | Nan::ThrowTypeError("Second argument must be a function"); 379 | return; 380 | } 381 | v8::Local callback = info[1].As(); 382 | 383 | CloseBaton* baton = new CloseBaton(); 384 | memset(baton, 0, sizeof(CloseBaton)); 385 | baton->fd = fd; 386 | baton->callback = new Nan::Callback(callback); 387 | 388 | uv_work_t* req = new uv_work_t(); 389 | req->data = baton; 390 | uv_queue_work(uv_default_loop(), req, EIO_Close, (uv_after_work_cb)EIO_AfterClose); 391 | 392 | return; 393 | } 394 | 395 | void EIO_AfterClose(uv_work_t* req) { 396 | Nan::HandleScope scope; 397 | 398 | CloseBaton* data = static_cast(req->data); 399 | 400 | v8::Local argv[1]; 401 | if(data->errorString[0]) { 402 | argv[0] = v8::Exception::Error(Nan::New(data->errorString).ToLocalChecked()); 403 | } else { 404 | argv[0] = Nan::Undefined(); 405 | 406 | // We don't have an error, so clean up the write queue for that fd 407 | 408 | _WriteQueue *q = qForFD(data->fd); 409 | if (q) { 410 | q->lock(); 411 | QueuedWrite &write_queue = q->get(); 412 | while (!write_queue.empty()) { 413 | QueuedWrite *del_q = write_queue.next; 414 | del_q->baton->buffer.Reset(); 415 | del_q->remove(); 416 | } 417 | q->unlock(); 418 | 419 | deleteQForFD(data->fd); 420 | } 421 | 422 | } 423 | data->callback->Call(1, argv); 424 | 425 | delete data->callback; 426 | delete data; 427 | delete req; 428 | } 429 | 430 | NAN_METHOD(List) { 431 | 432 | // callback 433 | if(!info[0]->IsFunction()) { 434 | Nan::ThrowTypeError("First argument must be a function"); 435 | return; 436 | } 437 | v8::Local callback = info[0].As(); 438 | 439 | ListBaton* baton = new ListBaton(); 440 | strcpy(baton->errorString, ""); 441 | baton->callback = new Nan::Callback(callback); 442 | 443 | uv_work_t* req = new uv_work_t(); 444 | req->data = baton; 445 | uv_queue_work(uv_default_loop(), req, EIO_List, (uv_after_work_cb)EIO_AfterList); 446 | 447 | return; 448 | } 449 | 450 | void EIO_AfterList(uv_work_t* req) { 451 | Nan::HandleScope scope; 452 | 453 | ListBaton* data = static_cast(req->data); 454 | 455 | v8::Local argv[2]; 456 | if(data->errorString[0]) { 457 | argv[0] = v8::Exception::Error(Nan::New(data->errorString).ToLocalChecked()); 458 | argv[1] = Nan::Undefined(); 459 | } else { 460 | v8::Local results = Nan::New(); 461 | int i = 0; 462 | for(std::list::iterator it = data->results.begin(); it != data->results.end(); ++it, i++) { 463 | v8::Local item = Nan::New(); 464 | Nan::Set(item, Nan::New("comName").ToLocalChecked(), Nan::New((*it)->comName.c_str()).ToLocalChecked()); 465 | Nan::Set(item, Nan::New("manufacturer").ToLocalChecked(), Nan::New((*it)->manufacturer.c_str()).ToLocalChecked()); 466 | Nan::Set(item, Nan::New("serialNumber").ToLocalChecked(), Nan::New((*it)->serialNumber.c_str()).ToLocalChecked()); 467 | Nan::Set(item, Nan::New("pnpId").ToLocalChecked(), Nan::New((*it)->pnpId.c_str()).ToLocalChecked()); 468 | Nan::Set(item, Nan::New("locationId").ToLocalChecked(), Nan::New((*it)->locationId.c_str()).ToLocalChecked()); 469 | Nan::Set(item, Nan::New("vendorId").ToLocalChecked(), Nan::New((*it)->vendorId.c_str()).ToLocalChecked()); 470 | Nan::Set(item, Nan::New("productId").ToLocalChecked(), Nan::New((*it)->productId.c_str()).ToLocalChecked()); 471 | Nan::Set(results, i, item); 472 | } 473 | argv[0] = Nan::Undefined(); 474 | argv[1] = results; 475 | } 476 | data->callback->Call(2, argv); 477 | 478 | delete data->callback; 479 | for(std::list::iterator it = data->results.begin(); it != data->results.end(); ++it) { 480 | delete *it; 481 | } 482 | delete data; 483 | delete req; 484 | } 485 | 486 | NAN_METHOD(Flush) { 487 | 488 | // file descriptor 489 | if(!info[0]->IsInt32()) { 490 | Nan::ThrowTypeError("First argument must be an int"); 491 | return; 492 | } 493 | int fd = info[0]->ToInt32()->Int32Value(); 494 | 495 | // callback 496 | if(!info[1]->IsFunction()) { 497 | Nan::ThrowTypeError("Second argument must be a function"); 498 | return; 499 | } 500 | v8::Local callback = info[1].As(); 501 | 502 | FlushBaton* baton = new FlushBaton(); 503 | memset(baton, 0, sizeof(FlushBaton)); 504 | baton->fd = fd; 505 | baton->callback = new Nan::Callback(callback); 506 | 507 | uv_work_t* req = new uv_work_t(); 508 | req->data = baton; 509 | uv_queue_work(uv_default_loop(), req, EIO_Flush, (uv_after_work_cb)EIO_AfterFlush); 510 | 511 | return; 512 | } 513 | 514 | void EIO_AfterFlush(uv_work_t* req) { 515 | Nan::HandleScope scope; 516 | 517 | FlushBaton* data = static_cast(req->data); 518 | 519 | v8::Local argv[2]; 520 | 521 | if(data->errorString[0]) { 522 | argv[0] = v8::Exception::Error(Nan::New(data->errorString).ToLocalChecked()); 523 | argv[1] = Nan::Undefined(); 524 | } else { 525 | argv[0] = Nan::Undefined(); 526 | argv[1] = Nan::New(data->result); 527 | } 528 | data->callback->Call(2, argv); 529 | 530 | delete data->callback; 531 | delete data; 532 | delete req; 533 | } 534 | 535 | NAN_METHOD(Set) { 536 | 537 | // file descriptor 538 | if(!info[0]->IsInt32()) { 539 | Nan::ThrowTypeError("First argument must be an int"); 540 | return; 541 | } 542 | int fd = info[0]->ToInt32()->Int32Value(); 543 | 544 | // options 545 | if(!info[1]->IsObject()) { 546 | Nan::ThrowTypeError("Second argument must be an object"); 547 | return; 548 | } 549 | v8::Local options = info[1]->ToObject(); 550 | 551 | // callback 552 | if(!info[2]->IsFunction()) { 553 | Nan::ThrowTypeError("Third argument must be a function"); 554 | return; 555 | } 556 | v8::Local callback = info[2].As(); 557 | 558 | SetBaton* baton = new SetBaton(); 559 | memset(baton, 0, sizeof(SetBaton)); 560 | baton->fd = fd; 561 | baton->callback = new Nan::Callback(callback); 562 | baton->brk = Nan::Get(options, Nan::New("brk").ToLocalChecked()).ToLocalChecked()->ToBoolean()->BooleanValue(); 563 | baton->rts = Nan::Get(options, Nan::New("rts").ToLocalChecked()).ToLocalChecked()->ToBoolean()->BooleanValue(); 564 | baton->cts = Nan::Get(options, Nan::New("cts").ToLocalChecked()).ToLocalChecked()->ToBoolean()->BooleanValue(); 565 | baton->dtr = Nan::Get(options, Nan::New("dtr").ToLocalChecked()).ToLocalChecked()->ToBoolean()->BooleanValue(); 566 | baton->dsr = Nan::Get(options, Nan::New("dsr").ToLocalChecked()).ToLocalChecked()->ToBoolean()->BooleanValue(); 567 | 568 | uv_work_t* req = new uv_work_t(); 569 | req->data = baton; 570 | uv_queue_work(uv_default_loop(), req, EIO_Set, (uv_after_work_cb)EIO_AfterSet); 571 | 572 | return; 573 | } 574 | 575 | void EIO_AfterSet(uv_work_t* req) { 576 | Nan::HandleScope scope; 577 | 578 | SetBaton* data = static_cast(req->data); 579 | 580 | v8::Local argv[2]; 581 | 582 | if(data->errorString[0]) { 583 | argv[0] = v8::Exception::Error(Nan::New(data->errorString).ToLocalChecked()); 584 | argv[1] = Nan::Undefined(); 585 | } else { 586 | argv[0] = Nan::Undefined(); 587 | argv[1] = Nan::New(data->result); 588 | } 589 | data->callback->Call(2, argv); 590 | 591 | delete data->callback; 592 | delete data; 593 | delete req; 594 | } 595 | 596 | NAN_METHOD(Drain) { 597 | 598 | // file descriptor 599 | if(!info[0]->IsInt32()) { 600 | Nan::ThrowTypeError("First argument must be an int"); 601 | return; 602 | } 603 | int fd = info[0]->ToInt32()->Int32Value(); 604 | 605 | // callback 606 | if(!info[1]->IsFunction()) { 607 | Nan::ThrowTypeError("Second argument must be a function"); 608 | return; 609 | } 610 | v8::Local callback = info[1].As(); 611 | 612 | DrainBaton* baton = new DrainBaton(); 613 | memset(baton, 0, sizeof(DrainBaton)); 614 | baton->fd = fd; 615 | baton->callback = new Nan::Callback(callback); 616 | 617 | uv_work_t* req = new uv_work_t(); 618 | req->data = baton; 619 | uv_queue_work(uv_default_loop(), req, EIO_Drain, (uv_after_work_cb)EIO_AfterDrain); 620 | 621 | return; 622 | } 623 | 624 | void EIO_AfterDrain(uv_work_t* req) { 625 | Nan::HandleScope scope; 626 | 627 | DrainBaton* data = static_cast(req->data); 628 | 629 | v8::Local argv[2]; 630 | 631 | if(data->errorString[0]) { 632 | argv[0] = v8::Exception::Error(Nan::New(data->errorString).ToLocalChecked()); 633 | argv[1] = Nan::Undefined(); 634 | } else { 635 | argv[0] = Nan::Undefined(); 636 | argv[1] = Nan::New(data->result); 637 | } 638 | data->callback->Call(2, argv); 639 | 640 | delete data->callback; 641 | delete data; 642 | delete req; 643 | } 644 | 645 | // Change request for ticket #401 - credit to @sguilly 646 | SerialPortParity NAN_INLINE(ToParityEnum(const v8::Local& v8str)) { 647 | Nan::HandleScope scope; 648 | Nan::Utf8String *str = new Nan::Utf8String(v8str); 649 | size_t count = strlen(**str); 650 | SerialPortParity parity = SERIALPORT_PARITY_NONE; 651 | if(!strncasecmp(**str, "none", count)) { 652 | parity = SERIALPORT_PARITY_NONE; 653 | } else if(!strncasecmp(**str, "even", count)) { 654 | parity = SERIALPORT_PARITY_EVEN; 655 | } else if(!strncasecmp(**str, "mark", count)) { 656 | parity = SERIALPORT_PARITY_MARK; 657 | } else if(!strncasecmp(**str, "odd", count)) { 658 | parity = SERIALPORT_PARITY_ODD; 659 | } else if(!strncasecmp(**str, "space", count)) { 660 | parity = SERIALPORT_PARITY_SPACE; 661 | } 662 | // delete[] str; 663 | return parity; 664 | } 665 | 666 | 667 | SerialPortStopBits NAN_INLINE(ToStopBitEnum(double stopBits)) { 668 | if(stopBits > 1.4 && stopBits < 1.6) { 669 | return SERIALPORT_STOPBITS_ONE_FIVE; 670 | } 671 | if(stopBits == 2) { 672 | return SERIALPORT_STOPBITS_TWO; 673 | } 674 | return SERIALPORT_STOPBITS_ONE; 675 | } 676 | 677 | extern "C" { 678 | void init (v8::Handle target) 679 | { 680 | Nan::HandleScope scope; 681 | Nan::SetMethod(target, "set", Set); 682 | Nan::SetMethod(target, "open", Open); 683 | Nan::SetMethod(target, "update", Update); 684 | Nan::SetMethod(target, "write", Write); 685 | Nan::SetMethod(target, "close", Close); 686 | Nan::SetMethod(target, "list", List); 687 | Nan::SetMethod(target, "flush", Flush); 688 | Nan::SetMethod(target, "drain", Drain); 689 | 690 | #ifndef WIN32 691 | SerialportPoller::Init(target); 692 | #endif 693 | } 694 | } 695 | 696 | NODE_MODULE(serialport, init); 697 | -------------------------------------------------------------------------------- /src/serialport_unix.cpp: -------------------------------------------------------------------------------- 1 | #ifndef WIN32 2 | #include "serialport.h" 3 | #include "serialport_poller.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #ifdef __APPLE__ 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | uv_mutex_t list_mutex; 18 | Boolean lockInitialised = FALSE; 19 | #endif 20 | 21 | #if defined(MAC_OS_X_VERSION_10_4) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4) 22 | #include 23 | #include 24 | #include 25 | #endif 26 | 27 | #if defined(__OpenBSD__) 28 | #include 29 | #endif 30 | 31 | #if defined(__linux__) 32 | #include 33 | #include 34 | #endif 35 | 36 | struct UnixPlatformOptions : OpenBatonPlatformOptions { 37 | public: 38 | uint8_t vmin; 39 | uint8_t vtime; 40 | }; 41 | 42 | OpenBatonPlatformOptions* ParsePlatformOptions(const v8::Local& options) { 43 | Nan::HandleScope scope; 44 | 45 | UnixPlatformOptions* result = new UnixPlatformOptions(); 46 | result->vmin = Nan::Get(options, Nan::New("vmin").ToLocalChecked()).ToLocalChecked()->ToInt32()->Int32Value(); 47 | result->vtime = Nan::Get(options, Nan::New("vtime").ToLocalChecked()).ToLocalChecked()->ToInt32()->Int32Value(); 48 | 49 | return result; 50 | } 51 | 52 | int ToBaudConstant(int baudRate); 53 | int ToDataBitsConstant(int dataBits); 54 | int ToStopBitsConstant(SerialPortStopBits stopBits); 55 | 56 | void AfterOpenSuccess(int fd, Nan::Callback* dataCallback, Nan::Callback* disconnectedCallback, Nan::Callback* errorCallback) { 57 | delete dataCallback; 58 | delete errorCallback; 59 | delete disconnectedCallback; 60 | } 61 | 62 | int ToBaudConstant(int baudRate) { 63 | switch (baudRate) { 64 | case 0: return B0; 65 | case 50: return B50; 66 | case 75: return B75; 67 | case 110: return B110; 68 | case 134: return B134; 69 | case 150: return B150; 70 | case 200: return B200; 71 | case 300: return B300; 72 | case 600: return B600; 73 | case 1200: return B1200; 74 | case 1800: return B1800; 75 | case 2400: return B2400; 76 | case 4800: return B4800; 77 | case 9600: return B9600; 78 | case 19200: return B19200; 79 | case 38400: return B38400; 80 | case 57600: return B57600; 81 | case 115200: return B115200; 82 | case 230400: return B230400; 83 | #if defined(__linux__) 84 | case 460800: return B460800; 85 | case 500000: return B500000; 86 | case 576000: return B576000; 87 | case 921600: return B921600; 88 | case 1000000: return B1000000; 89 | case 1152000: return B1152000; 90 | case 1500000: return B1500000; 91 | case 2000000: return B2000000; 92 | case 2500000: return B2500000; 93 | case 3000000: return B3000000; 94 | case 3500000: return B3500000; 95 | case 4000000: return B4000000; 96 | #endif 97 | } 98 | return -1; 99 | } 100 | 101 | #ifdef __APPLE__ 102 | typedef struct SerialDevice { 103 | char port[MAXPATHLEN]; 104 | char locationId[MAXPATHLEN]; 105 | char vendorId[MAXPATHLEN]; 106 | char productId[MAXPATHLEN]; 107 | char manufacturer[MAXPATHLEN]; 108 | char serialNumber[MAXPATHLEN]; 109 | } stSerialDevice; 110 | 111 | typedef struct DeviceListItem { 112 | struct SerialDevice value; 113 | struct DeviceListItem *next; 114 | int* length; 115 | } stDeviceListItem; 116 | #endif 117 | 118 | int ToDataBitsConstant(int dataBits) { 119 | switch (dataBits) { 120 | case 8: default: return CS8; 121 | case 7: return CS7; 122 | case 6: return CS6; 123 | case 5: return CS5; 124 | } 125 | return -1; 126 | } 127 | 128 | 129 | 130 | void EIO_Open(uv_work_t* req) { 131 | OpenBaton* data = static_cast(req->data); 132 | 133 | int flags = (O_RDWR | O_NOCTTY | O_NONBLOCK | O_CLOEXEC | O_SYNC); 134 | int fd = open(data->path, flags); 135 | 136 | if(-1 == setup(fd, data)){ 137 | return; 138 | } 139 | 140 | data->result = fd; 141 | } 142 | 143 | void EIO_Update(uv_work_t* req) { 144 | OpenBaton* data = static_cast(req->data); 145 | 146 | int fd = data->fd; 147 | 148 | if(-1 == setup(fd, data)){ 149 | return; 150 | } 151 | 152 | data->result = fd; 153 | } 154 | 155 | 156 | int setup(int fd, OpenBaton *data) { 157 | 158 | UnixPlatformOptions* platformOptions = static_cast(data->platformOptions); 159 | 160 | int baudRate = ToBaudConstant(data->baudRate); 161 | 162 | // #if not ( defined(MAC_OS_X_VERSION_10_4) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4) ) 163 | // if(baudRate == -1) { 164 | // snprintf(data->errorString, sizeof(data->errorString), "Invalid baud rate setting %d", data->baudRate); 165 | // return; 166 | // } 167 | // #endif 168 | 169 | int dataBits = ToDataBitsConstant(data->dataBits); 170 | if(dataBits == -1) { 171 | snprintf(data->errorString, sizeof(data->errorString), "Invalid data bits setting %d", data->dataBits); 172 | return -1; 173 | } 174 | 175 | 176 | // int flags = (O_RDWR | O_NOCTTY | O_NONBLOCK | O_CLOEXEC | O_SYNC); 177 | // if(data->hupcl == false) { 178 | // flags &= ~HUPCL; 179 | // } 180 | // int fd = open(data->path, flags); 181 | 182 | int flags = (O_RDWR | O_NOCTTY | O_NONBLOCK | O_CLOEXEC | O_SYNC); 183 | if(data->hupcl == false) { 184 | flags &= ~HUPCL; 185 | } 186 | fd = open(data->path, flags); 187 | 188 | 189 | if (fd == -1) { 190 | snprintf(data->errorString, sizeof(data->errorString), "Cannot open %s", data->path); 191 | return -1; 192 | } 193 | 194 | //Snow Leopard doesn't have O_CLOEXEC 195 | int cloexec = fcntl(fd, F_SETFD, FD_CLOEXEC); 196 | if (cloexec == -1) { 197 | snprintf(data->errorString, sizeof(data->errorString), "Cannot open %s", data->path); 198 | return -1; 199 | } 200 | 201 | // struct sigaction saio; 202 | // saio.sa_handler = sigio_handler; 203 | // sigemptyset(&saio.sa_mask); 204 | // saio.sa_flags = 0; 205 | // sigaction(SIGIO, &saio, NULL); 206 | 207 | // //all process to receive SIGIO 208 | // fcntl(fd, F_SETOWN, getpid()); 209 | // int flflags = fcntl(fd, F_GETFL); 210 | // fcntl(fd, F_SETFL, flflags | FNONBLOCK); 211 | 212 | struct termios options; 213 | // Set baud and other configuration. 214 | tcgetattr(fd, &options); 215 | 216 | // Removing check for valid BaudRates due to ticket: #140 217 | // #if not ( defined(MAC_OS_X_VERSION_10_4) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4) ) 218 | // Specify the baud rate 219 | 220 | 221 | // On linux you can alter the meaning of B38400 to mean a custom baudrate... 222 | #if defined(__linux__) && defined(ASYNC_SPD_CUST) 223 | if (baudRate == -1) { 224 | struct serial_struct serinfo; 225 | serinfo.reserved_char[0] = 0; 226 | if (ioctl(fd, TIOCGSERIAL, &serinfo) != -1) { 227 | serinfo.flags &= ~ASYNC_SPD_MASK; 228 | serinfo.flags |= ASYNC_SPD_CUST; 229 | serinfo.custom_divisor = (serinfo.baud_base + (data->baudRate / 2)) / data->baudRate; 230 | if (serinfo.custom_divisor < 1) 231 | serinfo.custom_divisor = 1; 232 | 233 | ioctl(fd, TIOCSSERIAL, &serinfo); 234 | ioctl(fd, TIOCGSERIAL, &serinfo); 235 | // if (serinfo.custom_divisor * rate != serinfo.baud_base) { 236 | // warnx("actual baudrate is %d / %d = %f", 237 | // serinfo.baud_base, serinfo.custom_divisor, 238 | // (float)serinfo.baud_base / serinfo.custom_divisor); 239 | // } 240 | } 241 | 242 | // Now we use "B38400" to trigger the special baud rate. 243 | baudRate = B38400; 244 | } 245 | #endif 246 | 247 | if (baudRate != -1) { 248 | cfsetispeed(&options, baudRate); 249 | cfsetospeed(&options, baudRate); 250 | } 251 | 252 | // Removing check for valid BaudRates due to ticket: #140 253 | // #endif 254 | 255 | /* 256 | IGNPAR : ignore bytes with parity errors 257 | */ 258 | options.c_iflag = IGNPAR; 259 | 260 | /* 261 | ICRNL : map CR to NL (otherwise a CR input on the other computer 262 | will not terminate input) 263 | */ 264 | // Pulling this for now. It should be an option, however. -Giseburt 265 | //options.c_iflag = ICRNL; 266 | 267 | // otherwise make device raw (no other input processing) 268 | 269 | 270 | // Specify data bits 271 | options.c_cflag &= ~CSIZE; 272 | options.c_cflag |= dataBits; 273 | 274 | options.c_cflag &= ~(CRTSCTS); 275 | 276 | if (data->rtscts) { 277 | options.c_cflag |= CRTSCTS; 278 | // evaluate specific flow control options 279 | } 280 | 281 | options.c_iflag &= ~(IXON | IXOFF | IXANY); 282 | 283 | if (data->xon) { 284 | options.c_iflag |= IXON; 285 | } 286 | 287 | if (data->xoff) { 288 | options.c_iflag |= IXOFF; 289 | } 290 | 291 | if (data->xany) { 292 | options.c_iflag |= IXANY; 293 | } 294 | 295 | 296 | switch (data->parity) 297 | { 298 | case SERIALPORT_PARITY_NONE: 299 | options.c_cflag &= ~PARENB; 300 | // options.c_cflag &= ~CSTOPB; 301 | // options.c_cflag &= ~CSIZE; 302 | // options.c_cflag |= CS8; 303 | break; 304 | case SERIALPORT_PARITY_ODD: 305 | options.c_cflag |= PARENB; 306 | options.c_cflag |= PARODD; 307 | // options.c_cflag &= ~CSTOPB; 308 | // options.c_cflag &= ~CSIZE; 309 | // options.c_cflag |= CS7; 310 | break; 311 | case SERIALPORT_PARITY_EVEN: 312 | options.c_cflag |= PARENB; 313 | options.c_cflag &= ~PARODD; 314 | // options.c_cflag &= ~CSTOPB; 315 | // options.c_cflag &= ~CSIZE; 316 | // options.c_cflag |= CS7; 317 | break; 318 | default: 319 | snprintf(data->errorString, sizeof(data->errorString), "Invalid parity setting %d", data->parity); 320 | close(fd); 321 | return -1; 322 | } 323 | 324 | switch(data->stopBits) { 325 | case SERIALPORT_STOPBITS_ONE: 326 | options.c_cflag &= ~CSTOPB; 327 | break; 328 | case SERIALPORT_STOPBITS_TWO: 329 | options.c_cflag |= CSTOPB; 330 | break; 331 | default: 332 | snprintf(data->errorString, sizeof(data->errorString), "Invalid stop bits setting %d", data->stopBits); 333 | close(fd); 334 | return -1; 335 | } 336 | 337 | options.c_cflag |= CLOCAL; //ignore status lines 338 | options.c_cflag |= CREAD; //enable receiver 339 | options.c_cflag |= HUPCL; //drop DTR (i.e. hangup) on close 340 | 341 | // Raw output 342 | options.c_oflag = 0; 343 | 344 | // ICANON makes partial lines not readable. It should be otional. 345 | // It works with ICRNL. -Giseburt 346 | options.c_lflag = 0; //ICANON; 347 | 348 | options.c_cc[VMIN]= platformOptions->vmin; 349 | options.c_cc[VTIME]= platformOptions->vtime; 350 | 351 | // removed this unneeded sleep. 352 | // sleep(1); 353 | tcflush(fd, TCIFLUSH); 354 | tcsetattr(fd, TCSANOW, &options); 355 | 356 | // On OS X, starting in Tiger, we can set a custom baud rate, as follows: 357 | #if defined(MAC_OS_X_VERSION_10_4) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4) 358 | if (baudRate == -1) { 359 | speed_t speed = data->baudRate; 360 | if (ioctl(fd, IOSSIOSPEED, &speed) == -1) { 361 | snprintf(data->errorString, sizeof(data->errorString), "Error %s calling ioctl( ..., IOSSIOSPEED, %ld )", strerror(errno), speed ); 362 | } 363 | } 364 | #endif 365 | 366 | return 1; 367 | 368 | } 369 | 370 | void EIO_Write(uv_work_t* req) { 371 | QueuedWrite* queuedWrite = static_cast(req->data); 372 | WriteBaton* data = static_cast(queuedWrite->baton); 373 | 374 | data->result = 0; 375 | errno = 0; 376 | 377 | // We carefully *DON'T* break out of this loop. 378 | do { 379 | if ((data->result = write(data->fd, data->bufferData + data->offset, data->bufferLength - data->offset)) == -1) { 380 | if (errno == EAGAIN || errno == EWOULDBLOCK) 381 | return; 382 | 383 | // The write call might be interrupted, if it is we just try again immediately. 384 | if (errno != EINTR) { 385 | snprintf(data->errorString, sizeof(data->errorString), "Error %s calling write(...)", strerror(errno) ); 386 | return; 387 | } 388 | 389 | // try again... 390 | continue; 391 | } 392 | // there wasn't an error, do the math on what we actually wrote... 393 | else { 394 | data->offset += data->result; 395 | } 396 | 397 | // if we get there, we really don't want to loop 398 | // break; 399 | } while (data->bufferLength > data->offset); 400 | } 401 | 402 | void EIO_Close(uv_work_t* req) { 403 | CloseBaton* data = static_cast(req->data); 404 | 405 | // printf(">>>> close fd %d\n", data->fd); 406 | 407 | // fcntl(data->fd, F_SETFL, FNONBLOCK); 408 | 409 | ssize_t r; 410 | 411 | r = close(data->fd); 412 | 413 | // printf(">>>> closed fd %d (err: %d)\n", data->fd, errno); 414 | 415 | if (r && r != EBADF) 416 | snprintf(data->errorString, sizeof(data->errorString), "Unable to close fd %d, errno: %d", data->fd, errno); 417 | } 418 | 419 | #ifdef __APPLE__ 420 | 421 | // Function prototypes 422 | static kern_return_t FindModems(io_iterator_t *matchingServices); 423 | static io_service_t GetUsbDevice(io_service_t service); 424 | static stDeviceListItem* GetSerialDevices(); 425 | 426 | 427 | static kern_return_t FindModems(io_iterator_t *matchingServices) 428 | { 429 | kern_return_t kernResult; 430 | CFMutableDictionaryRef classesToMatch; 431 | classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue); 432 | if (classesToMatch != NULL) 433 | { 434 | CFDictionarySetValue(classesToMatch, 435 | CFSTR(kIOSerialBSDTypeKey), 436 | CFSTR(kIOSerialBSDAllTypes)); 437 | } 438 | 439 | kernResult = IOServiceGetMatchingServices(kIOMasterPortDefault, classesToMatch, matchingServices); 440 | 441 | return kernResult; 442 | } 443 | 444 | static io_service_t GetUsbDevice(io_service_t service) 445 | { 446 | IOReturn status; 447 | io_iterator_t iterator = 0; 448 | io_service_t device = 0; 449 | 450 | if (!service) { 451 | return device; 452 | } 453 | 454 | status = IORegistryEntryCreateIterator(service, 455 | kIOServicePlane, 456 | (kIORegistryIterateParents | kIORegistryIterateRecursively), 457 | &iterator); 458 | 459 | if (status == kIOReturnSuccess) 460 | { 461 | io_service_t currentService; 462 | while ((currentService = IOIteratorNext(iterator)) && device == 0) 463 | { 464 | io_name_t serviceName; 465 | status = IORegistryEntryGetNameInPlane(currentService, kIOServicePlane, serviceName); 466 | if (status == kIOReturnSuccess && IOObjectConformsTo(currentService, kIOUSBDeviceClassName)) { 467 | device = currentService; 468 | } 469 | else { 470 | // Release the service object which is no longer needed 471 | (void) IOObjectRelease(currentService); 472 | } 473 | } 474 | 475 | // Release the iterator 476 | (void) IOObjectRelease(iterator); 477 | } 478 | 479 | return device; 480 | } 481 | 482 | static void ExtractUsbInformation(stSerialDevice *serialDevice, IOUSBDeviceInterface **deviceInterface) 483 | { 484 | kern_return_t kernResult; 485 | UInt32 locationID; 486 | kernResult = (*deviceInterface)->GetLocationID(deviceInterface, &locationID); 487 | if (KERN_SUCCESS == kernResult) 488 | { 489 | snprintf(serialDevice->locationId, 11, "0x%08x", locationID); 490 | } 491 | 492 | UInt16 vendorID; 493 | kernResult = (*deviceInterface)->GetDeviceVendor(deviceInterface, &vendorID); 494 | if (KERN_SUCCESS == kernResult) 495 | { 496 | snprintf(serialDevice->vendorId, 7, "0x%04x", vendorID); 497 | } 498 | 499 | UInt16 productID; 500 | kernResult = (*deviceInterface)->GetDeviceProduct(deviceInterface, &productID); 501 | if (KERN_SUCCESS == kernResult) 502 | { 503 | snprintf(serialDevice->productId, 7, "0x%04x", productID); 504 | } 505 | } 506 | 507 | static stDeviceListItem* GetSerialDevices() 508 | { 509 | kern_return_t kernResult; 510 | io_iterator_t serialPortIterator; 511 | char bsdPath[MAXPATHLEN]; 512 | 513 | FindModems(&serialPortIterator); 514 | 515 | io_service_t modemService; 516 | kernResult = KERN_FAILURE; 517 | Boolean modemFound = false; 518 | 519 | // Initialize the returned path 520 | *bsdPath = '\0'; 521 | 522 | stDeviceListItem* devices = NULL; 523 | stDeviceListItem* lastDevice = NULL; 524 | int length = 0; 525 | 526 | while ((modemService = IOIteratorNext(serialPortIterator))) 527 | { 528 | CFTypeRef bsdPathAsCFString; 529 | 530 | bsdPathAsCFString = IORegistryEntrySearchCFProperty(modemService, kIOServicePlane, CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, kIORegistryIterateRecursively); 531 | 532 | if (bsdPathAsCFString) 533 | { 534 | Boolean result; 535 | 536 | // Convert the path from a CFString to a C (NUL-terminated) 537 | 538 | result = CFStringGetCString((CFStringRef) bsdPathAsCFString, 539 | bsdPath, 540 | sizeof(bsdPath), 541 | kCFStringEncodingUTF8); 542 | CFRelease(bsdPathAsCFString); 543 | 544 | if (result) 545 | { 546 | stDeviceListItem *deviceListItem = (stDeviceListItem*) malloc(sizeof(stDeviceListItem)); 547 | stSerialDevice *serialDevice = &(deviceListItem->value); 548 | strcpy(serialDevice->port, bsdPath); 549 | memset(serialDevice->locationId, 0, sizeof(serialDevice->locationId)); 550 | memset(serialDevice->vendorId, 0, sizeof(serialDevice->vendorId)); 551 | memset(serialDevice->productId, 0, sizeof(serialDevice->productId)); 552 | serialDevice->manufacturer[0] = '\0'; 553 | serialDevice->serialNumber[0] = '\0'; 554 | deviceListItem->next = NULL; 555 | deviceListItem->length = &length; 556 | 557 | if (devices == NULL) { 558 | devices = deviceListItem; 559 | } 560 | else { 561 | lastDevice->next = deviceListItem; 562 | } 563 | 564 | lastDevice = deviceListItem; 565 | length++; 566 | 567 | modemFound = true; 568 | kernResult = KERN_SUCCESS; 569 | 570 | uv_mutex_lock(&list_mutex); 571 | 572 | io_service_t device = GetUsbDevice(modemService); 573 | 574 | if (device) { 575 | CFStringRef manufacturerAsCFString = (CFStringRef) IORegistryEntryCreateCFProperty(device, 576 | CFSTR(kUSBVendorString), 577 | kCFAllocatorDefault, 578 | 0); 579 | 580 | if (manufacturerAsCFString) 581 | { 582 | Boolean result; 583 | char manufacturer[MAXPATHLEN]; 584 | 585 | // Convert from a CFString to a C (NUL-terminated) 586 | result = CFStringGetCString(manufacturerAsCFString, 587 | manufacturer, 588 | sizeof(manufacturer), 589 | kCFStringEncodingUTF8); 590 | 591 | if (result) { 592 | strcpy(serialDevice->manufacturer, manufacturer); 593 | } 594 | 595 | CFRelease(manufacturerAsCFString); 596 | } 597 | 598 | CFStringRef serialNumberAsCFString = (CFStringRef) IORegistryEntrySearchCFProperty(device, 599 | kIOServicePlane, 600 | CFSTR(kUSBSerialNumberString), 601 | kCFAllocatorDefault, 602 | kIORegistryIterateRecursively); 603 | 604 | if (serialNumberAsCFString) 605 | { 606 | Boolean result; 607 | char serialNumber[MAXPATHLEN]; 608 | 609 | // Convert from a CFString to a C (NUL-terminated) 610 | result = CFStringGetCString(serialNumberAsCFString, 611 | serialNumber, 612 | sizeof(serialNumber), 613 | kCFStringEncodingUTF8); 614 | 615 | if (result) { 616 | strcpy(serialDevice->serialNumber, serialNumber); 617 | } 618 | 619 | CFRelease(serialNumberAsCFString); 620 | } 621 | 622 | IOCFPlugInInterface **plugInInterface = NULL; 623 | SInt32 score; 624 | HRESULT res; 625 | 626 | IOUSBDeviceInterface **deviceInterface = NULL; 627 | 628 | kernResult = IOCreatePlugInInterfaceForService(device, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, 629 | &plugInInterface, &score); 630 | 631 | if ((kIOReturnSuccess != kernResult) || !plugInInterface) { 632 | continue; 633 | } 634 | 635 | // Use the plugin interface to retrieve the device interface. 636 | res = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), 637 | (LPVOID*) &deviceInterface); 638 | 639 | // Now done with the plugin interface. 640 | (*plugInInterface)->Release(plugInInterface); 641 | 642 | if (res || deviceInterface == NULL) { 643 | continue; 644 | } 645 | 646 | // Extract the desired Information 647 | ExtractUsbInformation(serialDevice, deviceInterface); 648 | 649 | // Release the Interface 650 | (*deviceInterface)->Release(deviceInterface); 651 | 652 | // Release the device 653 | (void) IOObjectRelease(device); 654 | } 655 | 656 | uv_mutex_unlock(&list_mutex); 657 | } 658 | } 659 | 660 | // Release the io_service_t now that we are done with it. 661 | (void) IOObjectRelease(modemService); 662 | } 663 | 664 | IOObjectRelease(serialPortIterator); // Release the iterator. 665 | 666 | return devices; 667 | } 668 | 669 | #endif 670 | 671 | void EIO_List(uv_work_t* req) { 672 | // This code exists in javascript for unix platforms 673 | 674 | #ifdef __APPLE__ 675 | if(!lockInitialised) 676 | { 677 | uv_mutex_init(&list_mutex); 678 | lockInitialised = TRUE; 679 | } 680 | 681 | ListBaton* data = static_cast(req->data); 682 | 683 | stDeviceListItem* devices = GetSerialDevices(); 684 | 685 | if (*(devices->length) > 0) 686 | { 687 | stDeviceListItem* next = devices; 688 | 689 | for (int i = 0, len = *(devices->length); i < len; i++) { 690 | stSerialDevice device = (* next).value; 691 | 692 | ListResultItem* resultItem = new ListResultItem(); 693 | resultItem->comName = device.port; 694 | 695 | if (device.locationId != NULL) { 696 | resultItem->locationId = device.locationId; 697 | } 698 | if (device.vendorId != NULL) { 699 | resultItem->vendorId = device.vendorId; 700 | } 701 | if (device.productId != NULL) { 702 | resultItem->productId = device.productId; 703 | } 704 | if (device.manufacturer != NULL) { 705 | resultItem->manufacturer = device.manufacturer; 706 | } 707 | if (device.serialNumber != NULL) { 708 | resultItem->serialNumber = device.serialNumber; 709 | } 710 | data->results.push_back(resultItem); 711 | 712 | stDeviceListItem* current = next; 713 | 714 | if (next->next != NULL) 715 | { 716 | next = next->next; 717 | } 718 | 719 | free(current); 720 | } 721 | 722 | } 723 | 724 | #endif 725 | } 726 | 727 | void EIO_Flush(uv_work_t* req) { 728 | FlushBaton* data = static_cast(req->data); 729 | 730 | data->result = tcflush(data->fd, TCIFLUSH); 731 | } 732 | 733 | void EIO_Set(uv_work_t* req) { 734 | SetBaton* data = static_cast(req->data); 735 | 736 | int bits; 737 | ioctl( data->fd, TIOCMGET, &bits ); 738 | 739 | bits &= ~(TIOCM_RTS | TIOCM_CTS | TIOCM_DTR | TIOCM_DSR); 740 | 741 | if (data->rts) { 742 | bits |= TIOCM_RTS; 743 | } 744 | 745 | if (data->cts) { 746 | bits |= TIOCM_CTS; 747 | } 748 | 749 | if (data->dtr) { 750 | bits |= TIOCM_DTR; 751 | } 752 | 753 | if (data->dsr) { 754 | bits |= TIOCM_DSR; 755 | } 756 | 757 | //todo check these returns 758 | if (data->brk) { 759 | ioctl(data->fd, TIOCSBRK, NULL); 760 | }else{ 761 | ioctl(data->fd, TIOCCBRK, NULL); 762 | } 763 | 764 | data->result = ioctl(data->fd, TIOCMSET, &bits ); 765 | } 766 | 767 | void EIO_Drain(uv_work_t* req) { 768 | DrainBaton* data = static_cast(req->data); 769 | 770 | data->result = tcdrain(data->fd); 771 | } 772 | 773 | #endif 774 | --------------------------------------------------------------------------------