Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SerialPort Streams2 Implementation #431

Closed
wants to merge 30 commits into from

Conversation

joshperry
Copy link

These are my initial bullet points for what I'd like this PR to eventually consist of. This will change and, most likely, grow. I'm pushing this up early because I would absolutely love input/ideas on this. It's not quite ready to use yet, still working on tests and kind of hashing out plans on where and how to do things.

There are a couple issues that makes this lib mesh poorly with contemporary node.js code:

  • There are 3 ways to handle errors (global emitter, instance emitter, callbacks).
  • It implements most of the streaming logic itself which makes it much more complex than it need be.
  • Functions and events for reading and writing are inconsistent with node Streams2.
  • Implements the idea of Parsers when node was made to pipe streams of data, see Transform streams.
  • Implement a Duplex Streams2 stream
  • Make precondition errors more consistent
  • Error events are always emitted whether there is a callback or not
  • Remove callbacks from most functions and rely on stream events
  • Goodbye global event emitter
  • Goodbye opening port immediately from constructor, including callback parameter
  • Add openPort() module export for simultaneously creating and opening a port, similar to the net.createConnection for net.Socket.
  • Callback to open() is the same as .once(‘open’, …)
  • list() returns a Promise
  • Goodbye parsers, prefer transform streams

@reconbot
Copy link
Member

I'll look when I get home but <3 <3 <3
On Dec 30, 2014 7:27 PM, "Joshua Perry" [email protected] wrote:

These are my initial bullet points for what I'd like this PR to eventually
consist of. This will change and, most likely, grow. I'm pushing this up
early because I would absolutely love input/ideas on this. It's not quite
ready to use yet, still working on tests and kind of hashing out plans on
where and how to do things.

There are a couple issues that makes this lib mesh poorly with
contemporary node.js code:

  • There are 3 ways to handle errors (global emitter, instance emitter,
    callbacks).

  • It implements most of the streaming logic itself which makes it much

    more complex than it need be.

    Functions and events for reading and writing are inconsistent with

    node Streams2

    Implement a Duplex Streams2 stream

  • Make precondition errors more consistent

  • Error events are always emitted whether there is a callback or not

  • Remove callbacks from most functions and rely on stream events

  • Goodbye global event emitter

  • Goodbye opening port immediately from constructor, including
    callback parameter

  • Add openPort() module export for simultaneously creating and
    opening a port, similar to the net.createConnection for net.Socket.

  • Callback to open() is the same as .once(‘open’, …)

  • Goodbye parsers, prefer transform streams


You can merge this Pull Request by running

git pull https://github.com/prodatakey/node-serialport streams2

Or view, comment on, or merge it at:

#431
Commit Summary

  • Initial commit

File Changes

Patch Links:


Reply to this email directly or view it on GitHub
#431.

});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes are irrelevant.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Irrelevant unless you want the lint step in the build process to pass. It complains when there are mixed quotes, among other things.

@rwaldron
Copy link
Contributor

I'll test this with as many projects as I can tomorrow.

@joshperry
Copy link
Author

I pushed this pretty early, just wanted to start getting feedback. It's not quite ready to be used, and may actually not even build yet. Hoping to make some rapid progress in the next couple days.

@voodootikigod
Copy link
Collaborator

@joshperry great work thus far, are you building on mac, linux, or windows (we will want to retain support for all three)?

@joshperry
Copy link
Author

@voodootikigod I'm currently working on a Mac. Though, I'm not planning to make any changes to the native code so the existing state of cross-platform compatibility shouldn't be changed by this PR.

@voodootikigod
Copy link
Collaborator

Awesome!

Chris Williams

@voodootikigod http://twitter.com/voodootikigod | GitHub
http://github.com/voodootikigod

The things I make that you should check out:
SaferAging http://www.saferaging.com/ | JSConf http://jsconf.com/ |
RobotsConf http://robotsconf.com/ | RobotsWeekly
http://robotsweekly.com/

Help me end the negativity on the internet, share this
http://jsconf.eu/2011/an_end_to_negativity.html.

On Wed, Dec 31, 2014 at 10:39 AM, Joshua Perry [email protected]
wrote:

I'm currently working on a Mac. Though, I'm not planning to make any
changes to the native code so the existing state of cross-platform
compatibility shouldn't be changed by this PR.


Reply to this email directly or view it on GitHub
#431 (comment)
.

@joshperry
Copy link
Author

I had initially planned to make this backwards compatible while still implementing the new features. As I've kind of hacked through some changes to get familiar with the code, I just don't think that's going to be the best route. I think it would be best to branch the current v1 code and merge this to be released as a major version change; 2.0 probably.

I plan to add migration instructions in the Readme. The changes shouldn't be too complex, mainly cleaning up error handling, and the read/write functions; parsers can be easily ported using through2.

No global emitter
No open in constructor
`list()` returns promise
Got testing set up a little more.

Wrote tests and implemented the constructor.
The constructor now only takes stream options and inits the object.

Wrote tests and implemented the the `open` instance function.

Wrote tests and implementd the `open` and `openPort` module functions.
This is the recommended way to get an open SerialPort instance by passing options and an optional callback.

Opening a port now gets `comName` from the options object.
Opening callback is now subscribed to the open event.
@joshperry
Copy link
Author

Well, constructing, opening, and reading seem to be working well now. I need tests for the reading, I was just hacking on it to figure out how the native stuff worked and kind of neglected the tests.

Going to wrap up writing next, and then the shutdown, and flush; gracefully handling errors should finish things up.

I began a rewrite/factor of the native stuff to make the platforms more homogenous with each other and with the other Node streams (tcp, udp, files), but I'm going to leave those changes out of this branch for now. Maybe when I get a little more time and gumption I'll get to that. Instead, I just made some shims (UnixSource and WindowsSource) that wrangle the native interface into a consistent shape for the rest of the code.

@joshperry
Copy link
Author

I converted the terminal app to use streams and piping instead of dealing directly with data events. This should be a good example of how nice streams are to work with, well, streams of data.

@joshperry
Copy link
Author

Getting close to done. Opening, reading, and writing all seem to work well when used with the data event, as a stream, and/or with the standard read/write functions. Just need to go over error conditions and gracefully closing the stream/port when there's an issue. Then, of course, documentation.

It should be useable if any of you want to pull the code and play with it against a branch of a project you have set up.

@joshperry
Copy link
Author

Blarg. Doing some work on error and ending flows and realizing that the native Windows code is not in a good place.

First, it's push-only, so there's no way to control buffer backpressure. If you happen to have a rapid stream of data, at a high baud rate, you're going to blow some buffers, not that you'll know; ignorance is bliss as they say.

Secondly, the code goes catatonic in error conditions. Since we don't read from the native code, there is no path for communicating read errors. If you pull the USB device after opening the port, write just blocks indefinitely and doesn't return, throw, or callback with an error, ever.

Unfortunately this leaves us in a state where there's no way to write commercial-grade code for Windows on this foundation. Though, perhaps that's not the sort of thing that anyone is using this library for.

I guess, for now, that I'm just going to punt on errors in Windows until I attack the native codebase, but users should really be aware of this functional parity mismatch.

@reconbot
Copy link
Member

One of the things that has irked me for a while is that we let the platform differences sneak up into the app logic. I'd love to see an abstraction in place that helps smooth this out.

@joshperry
Copy link
Author

@reconbot Absolutely, and I've tried to do that here. The UnixSource and WindowsSource are meant to be that abstraction (at least for data flow) until I get the time to go into the native code. The biggest problem with that is that the Windows native code takes a reference to options.dataCallback when you call open() so that callback has to be set before that call. Because of this I have to create dataCallback outside of WindowsSource even though it's only needed on Windows.

@reconbot
Copy link
Member

Oh never mind you're completly on top of that. I should have said.

It's amazing how you've put a shim between the different hardware abstractions. It's been irking me for ages!

@reconbot
Copy link
Member

So here's the other side of things, browser serialport is doing a lot of work getting compatibility with node-serialport. I think a smaller api like you've exposed here would be all they'd have to do in this new design.

@voodootikigod
Copy link
Collaborator

Bringing in @phated and @jacobrosenthal to this convo.

Chris Williams

@voodootikigod http://twitter.com/voodootikigod | GitHub
http://github.com/voodootikigod

The things I make that you should check out:
SaferAging http://www.saferaging.com/ | JSConf http://jsconf.com/ |
RobotsConf http://robotsconf.com/ | RobotsWeekly
http://robotsweekly.com/

Help me end the negativity on the internet, share this
http://jsconf.eu/2011/an_end_to_negativity.html.

On Sun, Jan 18, 2015 at 4:06 PM, Francis Gulotta [email protected]
wrote:

So here's the other side of things, browser serialport is doing a lot of
work getting compatibility with node-serialport. I think a smaller api like
you've exposed here would be all they'd have to do in this new design.


Reply to this email directly or view it on GitHub
#431 (comment)
.

@phated
Copy link

phated commented Jan 18, 2015

As much as I like the idea of making this a stream interface, node streams are horrible. The largest glaring problem is one we see all the time in gulp: if a single error event is emitted, node destroys your entire stream pipeline. This requires a lot of hacky code to deal with and I don't know if the benefits outweigh that negative.

@joshperry
Copy link
Author

@phated I agree that node streams have a few idiosyncrasies, especially in error handling, but they are unique and useful. Very similar to piping in a shell, if one of the commands between pipes fails the whole thing has to be run again.

Gulp itself is also pretty unique, as far as using node streams is concerned. I don't see many end-user projects doing a large amount of piping. Have you guys talked about a better way to do a custom stream interface for Gulp that would solve the issues that node streams have?

What is nice to have though is a standard interface for streaming data to and from something so that a socket can be swapped for a USB connection, for a serial connection, or even a for a file. We gain a lot from going this direction even if the user is only using the readable event, read(), and write(), or the data event.

@phated
Copy link

phated commented Jan 19, 2015

@joshperry actually, @chrisdickinson put a lot of work into a stream error handling solution that got completely ignored or (worse) torn apart by the node community (see nodejs/node-v0.x-archive#8306).

My concerns are that node-serialport is fairly stable currently and doesn't get slammed with tons bug reports and such. By switching to this streams interface, that likely won't be true anymore. The amount of bug reports that get opened on gulp and many gulp plugins for the errors and unpiping far exceed any other.

Like I said, I like the idea of node-serialport being a stream, but it's the little things that will bite you. P.S. - just thought of another one: if you don't end your pipeline with a writeable (I think) stream, the backpressure will cause flowing of object mode streams after 16 events. We are trying to overcome this issue in gulp by using https://github.com/chrisdickinson/stream-exhaust which has its own set of problems.

@joshperry
Copy link
Author

@phated, node-serialport is already a stream. All I'm trying to do is make it a contemporary Streams2 stream. I picture most people still just gobbling up the data event like they do now.

I guess "fairly stable" is fair, but there are still all the horrible inconsistencies in the API and the deeper issues of the Windows native implementation that I'm trying to fix. To most users, if they are using serialport like the docs show, will only need to make maybe a 1LOC change to use v2, if they decide to upgrade the dep; otherwise they stay blissfully happy with their fairly stable v1 branch.

@phated
Copy link

phated commented Jan 19, 2015

Yes, I understand more than most the inconsistencies, but what about the parsers being through2 streams? That doesn't stay the same and if your pipeline explodes, "oh well"?

@joshperry
Copy link
Author

Well, I thought having a parser as a through stream was an elegant solution. Having the last, and only, stream in the pipe isn't going to cause much of a headache, it would only be errors propagated down from serialport at that point. But honestly, the idea of a parser really doesn't make any sense to me, it's basically implementing something that's better done outside of the serial port.

Why is this:

var parser = function(emitter, data) {
  // parsey, parse, parse
  emitter.emit('awesomesauce', parsed);
}
port = new SerialPort('/dev/usbthingy', { parser: parser });

consumer(port);

Better than (I don't like this way either, but it's a direct analog):

function parse(data) {
  // parsey, parse, parse
  this.emit('awesomesauce', parsed);
};
port.on('data', parse);

consumer(port);

I'm cool if we prefer to document this, or something similar, as the preferred way to deprecate parsers.

@jacobrosenthal
Copy link
Contributor

I'd love to separately talk about native code cleanup.
#443

@reconbot
Copy link
Member

I think the native code cleanup is important, but I think I'd actually like to steal the WindowsSource and UnixSource and bring them into our current serial port implementation. I think defining a consistent api at a lower level of abstraction will force us to refactor the native code a bit.

@voodootikigod
Copy link
Collaborator

Status on this is....

@reconbot
Copy link
Member

I'm going to close this issue due to age.

@joshperry I'm really sorry this didn't get merged at the time. The project has diverged far enough from 2014 that this PR is no longer mergable, but it still contains a ton of great ideas and goals and a reference for how it could work. I've opened #746 to discuss api changes for our next major release. I was considering an incremental change to the api to improve usability while giving us time to restructure the bindings layer, but I think that restructuring should consider what streams2 might demand of it.

@reconbot reconbot closed this Apr 10, 2016
@lock lock bot locked as resolved and limited conversation to collaborators Jan 18, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Development

Successfully merging this pull request may close these issues.

7 participants