Magic Internet Kit

Magic Internet Kit
Programmer's Guide


Serial Communications in Depth


Serial communications, for the purposes of the Magic Internet Kit, will be defined as using a raw data stream without intervening protocols like PPP. For example, using the modem to directly dial to a bulletin board system (BBS) or using the MagicBus port as a serial port counts as serial communications.

This chapter of the MIK Programmer's Guide will discuss the lower-level APIs that are built into Magic Cap for these types of serial communications. These APIs are below the level of the Magic Internet Kit framework, but the information presented here is valuable regardless of what level of APIs your application is using.

Using ConnectableMeans vs. low-level APIs

The first issue to discuss is which level of programming interfaces that your application should use. The low-level communications code in the system is what developers formerly had to use, but the Magic Internet Kit provides a higher level of abstraction that lets the developer program with one easy-to-use API and let the MIK framework worry about the messy and means-specific details.

In general, programming to the MIK framework is preferable to writing code directly for the low-level APIs. The Magic Internet Kit was designed to be both lightweight and modular, so your application only has to use the parts that it wants. In this case, selecting only serial communications support when creating your application will ensure that only the serial support classes are included, so there will be minimal overhead even though you get the fancy MIK interfaces.

The Serial and TCP/IP In Depth chapters are included with the Magic Internet Kit documentation since they may lend insight into the design choices made in the implementation of the MIK framework. Additionally, if you're trying to debug a comms problem, the information presented here can be extremely valuable. For these reasons, you should read these chapters even if you are using the high-level MIK framework.


Note: Pay special attention to the Detecting Loss of Carrier section; this information is very useful for all applications that use the modem.

Introducing the SerialServer and Modem classes

There are two key classes that you should know about for low-level serial communications: SerialServer and Modem. Both of these classes are used for accessing a communicator's built-in modem, and the SerialServer is used by itself for accessing the MagicBus port as a serial port.

The SerialServer is the lowest-level class that is used for serial communications, and this provides essential methods like Read, Write, OpenPort, and ClosePort. There are two serial servers in Magic Cap, referenced by the iSerialAServer and iSerialBServer indexicals, for the modem and MagicBus ports, respectively. You will rarely use iSerialAServer directly, but instead use the iModem indexical to access the modem-specific APIs.

The Modem class provides more functionality than the core serial server that is specific to using a Magic Cap communicator's built-in modem. For example, Modem's Connect and Disconnect methods will do special stuff like dialing a phone number and ensuring proper configuration of the modem. Essentially, the Modem class sits on top of the SerialServer class and worries about most of the lowest level details for you.

Using the modem

Using Magic Cap's modem without the aid of the high-level MIK framework is pretty easy; there isn't that much to be done, but you still have to make sure that you do it right. We'll first discuss basic modem usage, such as connecting and disconnecting, and then discuss related topics like detecting loss of carrier.

Connecting the modem

This part looks pretty complex, but in reality connecting the modem is a one-line operation with lots of error handling code around it. Connect time errors are all handled by means of exceptions, so if you are not familiar with exception handling you should read the "Handling Exceptions" chapter of Magic Cap Concepts before proceeding.

Step 1: See if there's a chance at connecting (preflighting)

The first step in trying to connect is to make sure that there's a chance that the connection could succeed. There are a few situations to check for that might immediately cause failure, so it's best to check for them before doing anything else.

First, check to see if the user has already set up a dialing location. If this is not set up, the Modem's Connect method will surely fail since it tries to be smart about prepending area codes and/or long-distance access numbers. This is the code that you should use to check for this case:

if (!SetupDialingLocation(iSystem)) /* fail! */

Second, check to make sure that the phone line is plugged in. Modem's CanConnect method will determine if this is the case:

if (!CanConnect(iModem)) /* fail! */

Third, check to make sure that someone else is not already using the modem:

if (InUse(iModem)) /* fail! */

Note that these checks do not guarantee that the above situations could not arise between checking here and trying to connect later. That's why we still need to put full-strength error handling on the connection itself. The above checks should still be made to make sure that we can detect obvious error cases before trying an all-out connect.


Source Code Note: Examples of using these "preflight checks" can be found in each of MIK's ConnectableMeans subclasses as part of the CanCreateConnection methods.

Step 2: Set up a TransferTicket object

The Modem class's Connect method expects a TransferTicket object as its second parameter, so we need to create one. The Means attribute of this object is the only one that we need to worry about, so don't be worried about the zillions of other fields. We'll create our ticket on the fly in our code, but as an alternative you could define one in your package's object instance file.

ObjectID phoneNumber = NewTransient(Text_, nil);
ObjectID telenumber  = NewTransient(Telenumber_, nil);
ObjectID means       = NewTransient(TelephoneMeans_, nil);
ObjectID ticket      = NewTransient(TransferTicket_, nil);

ReplaceTextWithLiteral(phoneNumber, "(800) 555-1212");
SetTelephone(telenumber, phoneNumber);
SetTelenumber(means, telenumber);
SetMeans(ticket, means);

Step 3: Set up the exception handlers

There are two main exceptions that you should expect to catch if anything goes wrong with connecting the modem: cannotOpenPort and commHardwareError. The first exception will be thrown if the modem port cannot be opened for some reason. The second exception will be thrown if something bad happens during or immediately after the connection process, for example if the remote modem does not answer the call. Here's the code that you need for catching these exceptions:

if (Catch(commHardwareError) != nilObject)
{
    /*
    ** If we are here, a commHardwareError exception got thrown.
    */
    return false; /* do what's appropriate here */
}
if (Catch(cannotOpenPort) != nilObject)
{
    /*
    ** If we are here, a cannotOpenPort exception got thrown. Note
    ** that the commHardwareError above did _not_ get thrown, so
    ** we need to do one Commit() to take it off the exception
    ** stack and commit its changes.
    */
    Commit();
    return false; /* do what's appropriate here */
}

Step 4: Try to connect!

Now that all the error handling code is in place, it's time to try connecting. Modem's Connect method is the method that we want to use.

Connect(iModem, ticket);


Note: Don't use the Modem class's ConnectToNumber or DialNumber methods. These methods are for system use only as the setup required before calling them is hardware dependent.

Step 5: Commit the exception handlers and clean up

If the connection attempt did not fail up to the exception handlers, then the connection attempt succeeded. At this point the exceptions handlers should be committed. The transfer ticket and its associated objects could also be destroyed as well unless you intend to use them again later.

Commit();        /* cannotOpenPort */
Commit();        /* commHardwareError */

Destroy(ticket); /* if you want to */

Using a live modem connection

Once the modem is connected, using it is pretty straightforward. The API is that of Magic Cap's Stream mixin class; Read is used for reading, Write is used for writing, and CountReadPending returns the number of bytes that are available for reading. One important note is that these methods are all synchronous, i.e. they block until they are completed. This blocking is not a big deal for writing to the modem since writing is usually a fast operation that does not have to wait for the other end. For reading, though, the issue is more important. If the remote server takes its time in sending you the data that you are expecting, then you either have to poll CountReadPending to avoid blocking the User Actor or run your code on its own thread. For more information on using your own thread, see the Multithreading with Actors section of this document.

If any of the stream methods cannot complete their task, they will return with whatever they could get done. If the method failed to complete due to a loss of carrier, for example the other end hanging up, then your code will have to detect this separately. See the later section on Detecting Loss of Carrier for more information.

Disconnecting the modem

Fortunately, disconnecting the modem is easier than connecting it. In typical usage, the Modem class's Disconnect method does the right thing. There is one interesting caveat here: if your code is blocked on a Read call, Disconnect will not return until the Read is completed. If you don't want to wait for the Read to return, or you know that the Read will never return, then disconnecting the modem is a tad more complex.

To disconnect the modem when other code is blocked on it, you have to abort the serial server to release the semaphores being held down by the outstanding reads or writes. Here's how to do that and then disconnect the modem:

ObjectID serialServer = Target(iModem);

if (Catch(serverAborted) == nilObject)
{
    Abort(serialServer);
    Commit();
}

ClosePort(serialServer);
OpenPort(serialServer);
Disconnect(iModem);

The first step is to abort the serial server, which you will note is found in the Target attribute of the modem, iModem. This will call ReleaseAndFail with a serverAborted exception on any semaphores that may be held down in the serial server, so the code should plan ahead and catch serverAborted exceptions. After that, closing and opening the serial server should get things in a reasonable state for calling Disconnect on the modem itself.


Note: The Abort method of the Modem class was designed to work in the case of blocked readers, but it doesn't. Unfortunately, code like the above must be used to get the modem unstuck.

Detecting Loss of Carrier

One fun aspect of communications is that anything can go wrong at any time. For example, the user can pull out the phone cord and mess up your modem connection or the remote modem could hang up the line. Your application should be prepared to deal with these situations by telling the modem that you want notification of a change in the carrier.

To tell the modem that you want to monitor the carrier, call MonitorDCD on the modem object with the second parameter set to the object that you want notified. The third parameter should be true to tell the modem to start notifying your target object of carrier changes.

Notification of carrier changes is provided via the callback CarrierChanged. The second parameter passed to this method by the modem, hasCarrier, tells your code if this change is a gain or loss of carrier. A typical CarrierChanged method might look like the following:

Method void
MyObject_CarrierChanged(ObjectID self, Boolean hasCarrier)
{
    if (!hasCarrier && MethinksIAmConnected(self))
    {
        /* I thought I was connected, but I guess I'm not anymore! */
        CleanUpAndGoHome(self);
    }
}

If carrier is lost in the middle of your communications session, you should still call Disconnect on the modem to get things in their proper state. Disconnecting the modem will also reset MonitorDCD so that it does not notify any objects of carrier changes, so be sure to set up MonitorDCD before every connection attempt.

Dispelling myths and stuff to avoid

The Modem class has been greatly misunderstood in the past, so this section discusses things that should not be done. Generally, one should use only the methodologies presented above for dealing with serial communications, so if in doubt about a method not mentioned in this chapter, don't use it.

AT commands are evil

The developer should never send AT commands directly to the modem from high level code. This is for compatibility reasons; AT commands are proprietary and each modem manufacturer's command set typically differs. Application code should never assume the type of modem that it is running on since the iModem indexical does not always refer to a communicator's built-in modem. Other third party applications can replace iModem with their own modem drivers, and the modems on the other end of the driver may not use the Rockwell command set that the internal modem uses.

Cruel and unusual punishment with SetBitRate


Note: This section applies only to total modem geeks.

If only one thought is to cross your mind when you wake up every morning, besides "not again," it should be "SetBitRate is evil."

This leads to an important question, namely, "huh?" HardwareStream_BitRate is a greatly misunderstood attribute lurking in Magic Cap, so an explanation of what it does is in order. The comments in the Server.Def class definition file say the following about HardwareStream's BitRate attribute: "Return current i/o bit rate, lower if mismatched." Maybe that's a tad terse. I'll say "BitRate and SetBitRate refer to the DTE/DCE speed. Note that this is not the same as the carrier speed." That's even more terse. Let me start by discussing some of the lower level details about modem communication.

First, There are two connections going on with device modems: the modem is talking to another modem, and it's also talking to the device CPU. I'll refer to the modem to modem speed as the carrier speed. I'll refer to the CPU to modem speed as the DTE/DCE speed. In the RS232 standard, DTE refers to Data Terminal Equipment, in this case the CPU, and DCE refers to Data Communications Equipment, or the modem. If we simply called the DTE a processor and DCE a modem, then a whole bunch of modem experts would be out of jobs since that would be too easy.

On the 2400 Baud devices like Sony's PIC-1000 and Motorola's Envoy, the modem and CPU do not hardware handshake, so the carrier speed and DTE/DCE speed have to be the same to avoid loss of data. SetBitRate(2400) sets the DTE/DCE speed to 2400 bps to make sure that the speeds are matched. Of course there's an edge case that can mess everything up: if the phone line is really noisy, or the modem on the other end is really slow, you might get a connection at 1200 Baud. In that case SetBitRate(2400) would not work.

On Sony's PIC-2000, the 14.4K modem and processor have hardware handshaking that lets them tell each other to stop sending bits for a while if one is bogged down. This hardware handshaking makes life much easier for the system; just set the DTE/DCE speed as high as it will go and let the handshaking prevent overflow. The only danger is that the DTE/DCE speed must be greater than or equal to the carrier speed, otherwise the following case could arise: the modem is connected at 14400 bps and the DTE/DCE speed is 2400. The modem can then receive data faster than it can send the data to the CPU, so it buffers everything it can. When the modem's receive buffer fills up, data loss occurs.

Here's the key: SetBitRate does not have anything to do with carrier (modem to modem) speed; it only controls DTE/DCE speed. There is no easy way to force carrier speeds in Magic Cap at the time. If you SetBitRate(2400) on a PIC-2000, it will still try to connect at 14,400.

Here's the best solution to the above complexities and problems: use Modem's Connect method. Connect knows about the hardware that it's running on, so it will handle all the device-specific details. It will handle carrier speed fallback on 2400 Baud devices, and it will make sure the DTE/DCE speed is maxed out on others. The user may not have control over the carrier speed, but that's often a good thing. Modems know how to talk to each other, so let them handle it. If the PIC-2000 tries to talk to a 9600 Baud modem, it will know to fall back and connect at 9600. The secret is to not worry about it.


Magic Internet Kit home page Programmer's Guide index