Details object vs Serial Port instance
The SerialPortDetails dictionary currently has the following properties:
- [ ] comName
- [ ] path
- [ ] pnpId
- [ ] manufacturer
- [ ] locationID
- [ ] vendorID
- [ ] productId
- [ ] serialNumber
However, an instance of SerialPort is currently lacking those properties. It seems annoying to have to request the available ports in order to find out the details about a port itself. You would have to find the matching object, etc. etc. which is just painful.
I suggest we add the above as attributes to instances of SerialPort itself.
So all of those seem to be specific to USB-to-serial adapters and not part of serial itself.
Right. That's what I thought. At the system level, is all that we can expect is a path?
Fundamentally, yes - just a path is all thats needed to open a serial port. Everything else is additional information that may or may not be available depending on the device.
Ok, so it sounds to me that listPorts() can only reliably return an array of strings that are the path names.
That's right
Francis Gulotta [email protected]
On Wed, Nov 6, 2013 at 4:21 PM, Marcos Caceres [email protected]:
Ok, so it sounds to me that listPorts() can only reliably return an array of strings that are the path names.
— Reply to this email directly or view it on GitHubhttps://github.com/whatwg/serial/issues/20#issuecomment-27914622 .
ok, cool. We are basically answering https://github.com/whatwg/serial/issues/19 :)
So, I'm going to move all those properties above to the SerialPort interface, but make them "nullable" (i.e., return null when not available).
Ok, so what I want to do here is have the serial port instance have an info attribute, that returns an ES6 Map compatible object. So:
var serial = new SerialPort("/some/path");
serial.info.forEach( (value, key) => console.log(key, value));
What might also work is having a .getInfo() method that returns an ES6 map of the metadata. That way, you always get a fresh copy of the metadata that can be manipulated without requiring the values of the map to be read only.
var serial = new SerialPort("/some/path")
, customInfo = serial.getInfo();
customInfo.forEach( (value, key) => console.log(key, value));
customInfo.delete("manufacturer");
customInfo.set("vendor", "Some One");
customInfo.set(controller, {someOtherObject})
Option 2 above could be quite useful, IMO.
The SerialPortInformation map would be guaranteed to at least have path.
Yeah, either have an immutable object / dictionary, or have a getter that returns an instance of Map. I'm not sure however if there is any sensible use case where somebody wants to change the properties … either way that should not stop us from returning a Map.
@fbender so, immutable objects (proposal 1) kinda suck because they are not idiomatic: it's expected that if you have a map, it will behave like a map. Making the map immutable breaks that expectation because set, delete, etc. don't work as expected.
IMHO I don't think a getter function is necessary. If we make it read only then it's harder to extend but we can trust it's contents. I wouldn't worry about people shooting themselves in the foot, they'll find other ways if that's the goal.
Francis Gulotta [email protected]
On Thu, Nov 7, 2013 at 11:55 AM, Florian Bender [email protected]:
Yeah, either have an immutable object / dictionary, or have a getter that returns an instance of Map. I'm not sure however if there is any sensible use case where somebody wants to change the properties … either way that should not stop us from returning a Map.
— Reply to this email directly or view it on GitHubhttps://github.com/whatwg/serial/issues/20#issuecomment-27983476 .
I'm of the position that we should change the underlying primitives as little as possible (if at all) - unless we have to. It makes the code easier to maintain, and doesn't have any side effects if, for instance, the Map prototype is extended with new functionality in the future.
A getter makes it clear that the data tied to the port itself is immutable (by returning a copy of the internal Map), so you can never change it globally. This makes the API look more obvious though is not necessarily required …
Do we know of any examples of APIs that contain a getter that returns an immutable array or map object?
I was kinda basing the design on ES-402's Intl.Collator.prototype.resolvedOptions(), which returns a fresh object every time it's called.
@rwaldron, sorry to bother again - but what would be more idiomatic here? (Please see API proposal in https://github.com/whatwg/serial/issues/20#issuecomment-27962557 ). Having a getter that returns a frozen Map or having a method that returns a new Map every time? ... or should the properties of the "info" object just be folded into SerialPort?
@marcoscaceres if you return a Map via getter, the Map does not need to be frozen – that's why I'd use a getter, to actually let it return a standard Map (or Object) that is a copy of the internal Map, so it does not need to be immutable.
I'm worried that people will screw with it - and that will mean that the internal map and the JS map will differ (or worst, would lead to race conditions).
That's the idea behind returning a copy via the getter: It does not matter if the Map gets edited. You always get the "fresh" copy from the getter, and if you mess with that copy, that's up to you.
(I worried about the same, that's why I proposed the immutable object at first. Again, this is mitigated by returning a copy via the getter.)
You already said that, BTW:
What might also work is having a .getInfo() method that returns an ES6 map of the metadata. That way, you always get a fresh copy of the metadata that can be manipulated without requiring the values of the map to be read only.
@fbender That's the idea behind returning a copy via the getter: It does not matter if the Map gets edited. You always get the "fresh" copy from the getter, and if you mess with that copy, that's up to you.
+1
Spoke to @fbender on IRC, we had a bit of confusion over terminology. When I was saying getter I meant:
//object attribute.
Object.defineProperty(x,"someGetter", {get: function(){...}});
As opposed to "instance.someGetter()".
@rwaldron, are you saying we should use a proper ES getter to return a new instance every time. This, of course, means that:
fooSerial.info === fooSerial.info; // false
Which might catch people out (though that is also the same for getInfo(), but at least it's a bit more predictable that you are getting a fresh copy).
Using getInfo() also avoid the following gotcha (maybe, didn't help jQuery;)):
//generating redundant objects on every call.
if (serial.info.foo === someValue && serial.info.bar === someOtherValue) {}
//vs, more efficient
var info = serial.getInfo();
if (info.foo === someValue && info.bar === someOtherValue) {}
there might be other gotchas with looping while trying to read the values.
The terminology mistake is that you're not using the right terminology. The thing you described above is an "accessor" property.
I'm missing context for this bombardment of snippets, but I think this is pointless:
The terminology mistake is that you're not using the right terminology. The thing you described above is an "accessor" property.
I'm missing context for this bombardment of snippets, but I think this is pointless:
var serial = new SerialPort("/some/path")
, customInfo = serial.getInfo();
customInfo.forEach( (value, key) => console.log(key, value));
customInfo.delete("manufacturer");
customInfo.set("vendor", "Some One");
customInfo.set(controller, {someOtherObject})
These are just properties of that serial port object, so why over complicate?
var serial = new SerialPort("/some/path");
// readonly data properties, don't need to be accessors to be readonly. See example at end.
serial.comName;
serial.manufacturer;
serial.serialNumber;
serial.pnpId;
serial.locationId;
serial.vendorId;
serial.productId;
// serial instances should be Iterables, ie. implement @@iterator that
// yields [ key, value ]
for ([k, v] of serial) {
console.log(k, v);
}
// Using this API to connect to an Arduino Leonardo from OSX:
var leo = new SerialPort("/dev/cu.usbmodem1411");
console.dir(leo);
// output:
{
comName: "/dev/cu.usbmodem1411",
manufacturer: "Arduino LLC",
serialNumber: "",
pnpId: "",
locationId: "0x14100000",
vendorId: "0x2341",
productId: "0x0036"
}
for ([k, v] of leo) {
console.log(`${k} has a value of ${v}`);
}
// output:
comName has a value of /dev/cu.usbmodem1411
manufacturer has a value of Arduino LLC
serialNumber has a value of
pnpId has a value of
locationId has a value of 0x14100000
vendorId has a value of 0x2341
productId has a value of 0x0036
You could take it one step further and specify that these objects also implement keys, values, entries and then you get forEach for free.
This is a readonly data property example:
function Readonly() {
Object.defineProperty(this, "foo", {
value: "bar"
});
}
var ro = new Readonly();
ro.foo; // "bar"
ro.foo = "something else";
ro.foo; // "bar"
:+1:
Simple and efficient.
Like it, it's why I dragged ya in here @rwaldron :)
Sorry about the weird copy/paste shenanigans... I wrote that in an editor and pasted back, whoops!
So the problem remains that we can't guarantee the availability and consistency of the information of the serial port (except for path) - because of legacy RS232 support. Hence the original design of not having the properties on the Serial Interface and instead having the SerialPortDetails Map be populated on request - through getInfo().
However, I think we can, to a degree merge the iterator idea with the Serial interface. So you can still do:
// serial instances is Iterable
// yields [ key, value ]
for ([k, v] of serial) {
console.log(k, v);
}
By adding an iterator SerialPortDetails to the interface definition - which will handle the magic of making Serial port objects be iteratorable over their metadata (getInfo()).
The other option is for us to pick the standard set of attribute (as @rwaldron suggests) and set them to null. However, this still leaves us with the SerialPortDetails problem: there is lots of interesting data available depending on how you are interfacing with the serial port (USB, BlueTooth, etc.).
So make them null when there is no data for the property? Objects without a predictable shape are problematic.
@rwaldron exactly - we can't predict the availability of the properties on any of the platforms (except for path). Which is why I wanted to make it so only known properties end up on the object when you iterate over it.
So, hypothetical examples:
//connect rs232 - Windows XP
var serial = new SerialPort("COM1");
console.dir(rs232);
// output
{
path: "COM1",
locationId: "0x14100000"
}
//Connect over USB on MacOS
var macSerial = new SerialPort("/dev/cu.usbmodem1411");
console.log(macSerial);
// output:
{
path: "/dev/cu.usbmodem1411",
manufacturer: "Arduino LLC",
locationId: "0x14100000",
vendorId: "0x2341",
productId: "0x0036"
}
//Connect over USB on Windows
var winSerial = new SerialPort("COM5");
console.log(winSerial);
{
path: "COM5",
manufacturer: "Arduino LLC",
locationId: "0x14100000",
vendorId: "0x2341",
productId: "0x0036",
pnpid: "1234123"
}
Err... sorry, that should have been console.dir()
only known properties end up on the object when you iterate over it.
I don't know what you mean by "known properties"