![]() |
Magic Internet Kit
|
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.
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.
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.
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! */
if (!CanConnect(iModem)) /* fail! */
if (InUse(iModem)) /* fail! */
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);
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 */ }
Connect(iModem, ticket);
Commit(); /* cannotOpenPort */ Commit(); /* commHardwareError */ Destroy(ticket); /* if you want to */
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.
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);
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 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.