Serial.Open is a terrible API

| | Comments (15)

I apologize for Serial.Open. It's a terrible API. Thankfully, I can at least say that it's not my fault as this API has been around in REALbasic for eons (well before my time).

You see, the problem is one of expectations. When an API in REALbasic returns a boolean, the expectation on the user's part is that it's generally a property and not a method. This stems from the fact that in ancient times, the only way to achieve a read-only property was to use a method. So Serial.Open() as Boolean certainly seems like it is a property which tells the user whether the serial control is open or not.

Unfortunately, that's not the case. It's not a read-only property -- it's a method which opens the serial control and returns whether the call succeeded or not. It behaves nothing like any other APIs in REALbasic (with the possible exception of SelectColor). Because of these reasons, this API is an abomination.

Unfortunately, there's no way to solve the problem without breaking code, or coming up with an uglier API. You see, if we deprecated the Open call, we'd have to find a replacement call which is responsible for opening the serial device. But Open is the logical choice -- unfortunately, it's not available. If we were starting over from scratch, the API would look like this:

********** THIS ISN'T THE CASE ***********
Sub Serial.Open()
This is responsible for opening the serial device and fires the Error event (setting LastErrorCode) if the open fails.

Serial.IsOpen as Boolean (read-only)
Tells the user whether the port is open or not.

This would be consistent with other APIs in REALbasic (such as the networking APIs), and is also logical to use. It doesn't promote code which is incorrect, such as the current API does. The current API encourages people to write code like this:

****** DON'T DO THIS *********
if Serial1.Open then
// Blah blah blah
end if

This code can be right, or it can be wrong -- there's no way to tell without more context, which is the hallmark of a poorly designed API. For instance, if that code was by itself in a PushButton.Action event, then it's perfectly sane code that is correct. But if that code was in the Serial.DataAvailable event of Serial1, then it causes the serial device to close mid-communication!

The closest replacement I can think of would be to deprecate Serial.Open entirely (so it no longer autocompletes, isn't documented, and is generally shunned) and replace it with:

Sub Serial.Connect()
Serial.IsConnected as Boolean (read-only)

This API is somewhat clearer in that you are connecting the DTE (the computer) to the DCE (the serial device), and it is consistent with the networking APIs. However, it's still a bastardization of the term "connect" since that implies some sort of handshake and communication, which isn't the case with what the API would actually be doing.

So yes, it's quite a strange situation. It's unfortunate that Serial.Open is structured the way that it is, but it's a historical API that I see a lot of people run amok with. Sorry for that!

15 Comments

I suppose you could deprecate the serial control and add a new spiffySerial control with a new, improved API that is more intuitive.
You could even give it a Port property that emulates Serial.SerialPort

I agree with Steve.

Name the control/classes something different so developers don't get burned like they did when RB changed databases but called both 'REALdatabase'. Many were the curses thrown towards Austin when that happened. :)

Or just change the API ?

While I agree that you dnt want to willy nilly break code "just because" sometimes it makes the most sense and at those times you just do it.

The compiler will warn people appropriately when and if they upgrade and things can move forward from there.

@Norm -- Breaking code is the last resort option because everyone seems to like blaming REAL Software for "bugs" when it happens. Don't believe me? Check out the latest spate of bug reports because people had projects that used "Global" as a name. It wasn't reserved before, then it became reserved, and then we get a bunch of complaints.

Serial controls just aren't important enough to break every person's code who uses it.

And has REAL fixed those bug reports ? No. I would not expect you to. They're probably closed as this is intended functionality.

Serial.Open can be exactly the same and the change in the listbox when it started calling the various paint events for rows that did not exist just so the alternating row coloring could be done. Surprised a lot of people but in the end everyone made the change and it's now a known thing.

Sometimes breaking code IS the exact right choice becuase the existing API is so problematic can causes more issues. Kind of like the infamous

dim a , b as new date

which is now not allowed by the compiler

Short of doing that introduce a new serial control and then deal with the resulting confusion of which one people should use.

I'm not saying we never break code (sorry for the implication) -- I'm saying that we do it only as a last resort. The serial APIs *work* and a lot of people have functional serial code. And the API doesn't limit you in any way (it's not like you can't accomplish something due to the way the API is structured). So I doubt it would change. Also, I doubt there would ever be so drastic of a change as to rewrite the entire Serial API as that's a lot of overkill. I certainly wouldn't mind it as there are some other concepts which could stand to be revamped. But I just don't envision it happening.

The purpose of the original post was two-fold: (1) Warn new users of a common gotcha, (2) Show a bit of the thought process which goes into API design.

Obviously I misread that then.

I took it as "Thank god we're thinking of redoing this crappy API" in an upcoming release

And, I agree that the Serial.Open() as Boolean is prone to errors and misunderstandings. I think I even did hat once upn a time with some serial code til I realized it was wrong to do that.

Would be nice if is was, as you suggested, split up into Open() and IsOpen() as boolean

Hi Aaron,

Breaks are a necessary evil.
If you think you have to change that code and that action will break code, no deal: advertise the change in the release notes, add a large warning in all user list and if one guy cry "there's a bug" just tell him to read message xyz / the release notes.

Eventually, add in the warning (release notes) a piece of code for the new version.

I have no trouble with a better code even at a cost of a break in my project IF I HAVE BEEN ADVERTISED.

Bad code = cry
Good code = bravo
Better code = CONGRATULATIONS.

I understood the reason why you don't want to break code by changing the functionality of the current .Open method but I didn't understand the reasons why you couldn't add a new Boolean Property such as .IsOpen or maybe an integer .Status

@Ian -- The general idea is because an IsOpen call is really not needed. Once you call Open, the port remains so until you explicitly closed it. Since you control opening and closing, you already implicitly know the state (the call to Open returns a boolean telling you whether the open succeeded or not).

Having a status call is dangerous because it implies more information than is truly available. For instance, you may call Open and control the port, and then someone unplugs the cable. The Status wouldn't be able to notice that information (it'd be protocol-specific), so it'd still report open, which is both correct and incorrect. There are several other similar cases I can think of as well.

I've found that when working with serial devices, I usually "hope for the best, but plan for the worst." I assume the port remains open and functioning, but code very defensively and assume just about every point of communication will fail.

Apart from using the RB Serial control is there any other way to use RealBasic to commnuicate via RS232 ports on MacOS/Linux ?

@Ian -- aside from declares or writing a plugin, there's nothing else I can think of. Why are you looking to avoid the built-in serial control?

The serial control seems to operate with a double buffer. One buffer in the serial control the other lower down in the OS driver.

The property serial.BytesLeftToSend returns zero once all xmit bytes have left the serial control but there's still 4k in the OS driver over which serial control seems to have no control.

All I need is one function which tells me how many bytes are REALLY left to send.

By the way, xmitwait doesn't help. xmitwait returns as soon as the tx bytes have been transferred to the driver buffer which quickly builds up the data in the driver until it reaches 4k and at that point xmitwait doesn't return until all bytes have been sent from the driver buffer which, at low baud rates, can take quite a while blocking the application.

I want to be able to send small blocks of data reading the number of bytes left in the driver's output buffer and only send the next block once the previous block has been sent.

Hope I'm explaining this clearly.

Thanks

I'm more than happy to use Declares - been doing it for years in Win32 but I'm new to MacOS - I've discovered that all the serial stuff which was in InterfaceLib isn't in Carbon - Just like in Win32 you can't talk directly to hardware so they took it out. I've searched IOKit till I'm blue in the face but can't find any functions to do what I want. Maybe you could tell me what API it's in and give me a link to some function list and I'll do the rest - Thanks
Ian
PS- Sorry about the previous Anon post - Also the 4k buffer is in Linux - MacOs seems to have a

@Ian -- On OS X, I've always just used the BSD APIs for working with serial controls (ioctl, and the likes) which are in the System bundle, IIRC. But I recall that OS X has absolutely terrible serial support. They didn't even get arbitrary baud rates until 10.3 or 10.4...

Sorry I can't be of more help, but I've never had to work at the level you're trying to work at.

Leave a comment

Disclaimer

I'm currently an employee of REAL Software. My blog is mine. The opinions represented in this blog are mine as well and may not represent my employer's opinions. All original material is copyrighted and property of the author.

REALbasic® is a registered trademark of REAL Software, Inc. REAL SQL Server™ and Lingua™ are pending trademarks of REAL Software, Inc. All rights reserved.