Endpoint Details

Article Overview
Defining Endpoints
Opening Endpoints
Sending Data
Receiving Data
Closing Endpoints
Error Handling
Power Off Handling
Options
Where To Go From Here
Notes

Goals

This article is the longest in this course. It describes endpoints in detail from a functional point of view. While the technical information is similar to Chapter 4 of Newton Programmer's Guide: Communications, it is designed to be less of a reference book and more of a cookbook. For this purpose there are code fragments demonstrating certain techniques and options throughout the article but it is suggested that you look at a complete example, either from the DTS Sample code or from one of the labs available as part of this class.

At the end of this article you should have a clear idea of how to create and use endpoints to communicate between the Newton and an external device. The labs which are part of this class are meant to put flesh on the bare bones we build here.

Prerequisites

You should be an experienced NewtonScript programmer who is familiar with the Newton view system and the use of the Newton Toolkit (NTK). It is also better if you have some experience with communications programming whether on the Newton or another platform however this is not strictly necessary. Without experience with communications programming you will be taking on the double task of learning basic communications concepts as well as the Newton communication API.

Required Equipment

To write Newton endpoint code you must of course have a Newton and a desktop machine equipped with version 1.5 or later of the Newton Toolkit. You will need an appropriate cable to connect your desktop machine and the Newton for downloading packages from NTK. You may also need another cable and hardware (such as a modem) for running the communications code we provide as well as the code you write. NTK may be purchased from Apple and the necessary cables can usually be purchased from any store or mail order supplier which sells computer products.

Having a serial I/O PCMCIA card is also useful (but not necessary) as it makes debugging endpoint code much easier by allowing you to keep the Inspector connected while sending and receiving data. For details on this and other debugging issues see the article Debugging Endpoints.

For technical details on endpoints, the "must read" reference is the document: Newton Programmer's Guide: Communications, particularly Chapters 4 and 5 (Endpoints and Built-in Communication Tools). This document is available from the Newton Technical Information web site. This section is based primarily on the information in these chapters. It is probably also a good idea to have a copy of The NewtonScript Programming Language, The Newton Programmer's Guide: System Software 2.0 and Newton Toolkit User's Guide. Finally the Q&A's Newton 2.0 document has several very important descriptions of the interactions between the Newton operating system and an endpoint. All of these documents except the Q&As are available online at the Newton Technical Information web site. The Q&As are available from the Q&A ftp site.

Article Sections

In Defining Endpoints we describe the process of creating an endpoint frame. This includes a description of the data template introduced in the 2.0 system as well as how to create and apply endpoint options.

In the section Opening Endpoints we describe how to get an endpoint open and ready to send and receive data. This begins with creating an endpoint object from the definition and then binding it to a low-level communications tool. Once the endpoint object is created and bound, we will discuss using it to open a connection to an external device. This is the first place we will discuss asynchronous calls to endpoint methods as it is usually desirable to not force the user to wait for the completion of a connection.

In Sending Data we send data out through an opened endpoint connection. In this section we will discuss to various data forms and methods for sending data including the transfer of binary data and Virtual Binary Objects (VBOs) to send large chunks of data.

The Receiving Data section describes in detail the use of the inputSpec frame to control incoming data. In particular we will focus on each of several ways to use an inputSpec to get incoming data and describe some likely uses for these mechanisms.

In Closing Endpoints we will discuss how to close a connection when you are done sending and receiving data and then how to tear-down the endpoint created in the section on Opening Endpoints.

The Error Handling section talks about the different levels of error handling available for Newton endpoints and when each type of error handling is appropriate.

Power Off Handling is a necessary consideration for endpoint programming as we must deal with the issues created by the fact that the Newton can be put into a low-power sleep mode which purposefully terminate power consumptive activities such as communications.

The Option section discusses the use of the Option method to interface directly with the low-level communications tools and gives some examples of existing options used in the built-in tools.

Endpoint Overview

Endpoints are designed to provide a common application programming interface (API) regardless of the underlying data transfer mechanism. The idea is that once a connection is made to an external device, the code to read and write data will be fairly similar. The model used in this approach is a virtual pipeline where data is sent in a stream of bytes from one end of the pipeline (e.g., the Newton) to the other (e.g., a modem). We will see that this model, while very useful, is not 100% uniform between different transfer mechanisms.

The endpoint API also provides an abstract interface to low-level communications tools. When an endpoint goes through the bind phase of connecting, the appropriate low-level communications tool is launched. When it is connected, the low-level tool orchestrates the dialog with the external device to establish the options send from the endpoint object. This relationship is shown in the following diagram:



It can be important to keep this picture in mind as unlike most other Newton programming, the CommManager task runs in a separate, fully independent thread from the NewtonScript Task. This means that during such operations as connecting, disconnecting, receiving data as well as asynchronous sending of data, there may be a time-lag between the time the endpoint method is called and the action occurs. In particular, we must be careful not to tear down the endpoint interface before the disconnect and unbind calls have taken effect.

All NewtonScript applications run in a single thread.

For more on the relationship between the NewtonScript Task and the low-level comm tool, see the Q&A What Really Happens During Instantiate and Connect as well as the Q&A What Is Error Code -18003? The Q&A document is available from the Q&A ftp site.

A quick laundry list of the normal life cycle of an endpoint follows:
1. Define the endpoint frame
2. Instantiate it to create a NewtonScript endpoint object
3. Bind it to a low-level comm tool
4. Connect it to an external device making sure an inputSpec is created for it
5. Send and receive data with the open endpoint changing inputSpecs as necessary
6. Disconnect the endpoint from the external device
7. Break the connection between the endpoint and the low-level tool
8. Destroy the endpoint object

While this is a typical list of steps there are of course many variations in this process according to your needs and requirements. What follows is a detailed description of each of these points with code fragments demonstrating how they are implemented.
The first step in programming endpoints is to define the endpoint. This is done by creating a frame which describes the kind of endpoint you want along with the various endpoint specific options. Once this frame is created it can be used to create an endpoint object which can.

An endpoint definition frame contains the following items:
A _proto slot which defines the endpoint prototype being used
A configOptions slot which describes the endpoint type and settings
An optional encoding slot which describes how incoming data is mapped
An optional ExceptionHandler slot which describes how to handle
errors thrown during the normal operation of the endpoint
Any other slots which may be useful
We'll look at these slots one at a time.

In the 2.0 system the most commonly used system prototype for endpoints is protoBasicEndpoint. It completely replaces the 1.x system prototype protoEndpoint which is no longer defined in the 2.0 platform file for NTK though it is still defined in the system for backward compatibility. (In other words code compiled to run on a 1.x system using protoEndpoint will continue to run but cannot be re-compiled for the 2.0 platform.) There is also a protoStreamingEndpoint which is proto'ed from protoBasicEndpoint though it is not frequently used. The slots listed above are those which are set by programmers using protoBasicEndpoint.

The configOptions slot in the endpoint frame describes which low-level comm tool the endpoint will be connected to (e.g., serial, modem, AppleTalk, etc.), and usually has a series of tool-specific settings. configOptions is an array of frames, each of which describe information to be sent to a low-level communications tool. In pseudo-code, this might look something like this:

configOptions:[{tool specification},{speed settings}, {error correction option}]

In other words, it describes what kind of connection we are using and what settings and other options we want to use when making the connection.

For example, we might specify a serial connection for the endpoint in one frame and then follow it with a an option frame defining such things as the baud rate, parity, stop bits, etc., for the serial connection. Each of the configOptions frames has the following basic format:


type slot - describes whether this is an service (e.g., serial) or a setting (e.g.,
connection speed)
label slot - constant describing what attribute we are specifying
opCode slot - a slot describing whether the option is negotiable or not
form slot - symbol describing the form of the data slot, for options this should be
'template
result
slot - is set by the call to an endpoint method and describes success or failure of the attempted call
data slot- the data required for the option, this is defined using a data template as
described below


Here's an example of a frame from a configOptions array:

{ label: kCMOSerialIOParms,
type: 'option,
opCode: opSetRequired,
result: nil,
form: 'template,
data: {
arglist: [
k1StopBits, // 1 stop bit
kNoParity, // no parity bit
k8DataBits, // 8 data bits
k9600bps, ], // date rate in bps
typelist: ['struct,
'long, // stop bits
'long, // parity
'long, // data bits
'long, ], }, }, // bps
In this example we are setting the I/O parameters for a serial connection. It is defined as being of type 'option (that is, it is a setting or other supplemental information) and the opCode constant of opSetRequired says we must get exactly the settings requested to proceed. The data is in a template form and sets the stop bits, parity, number of data bits, and the speed of the connection desired is 9600 bits per second.

If we had set the opCode slot to opSetNegotiate than the requested settings would be negotiable between the Newton and the external device and may have returned with different values. In this case the result slot would have been set with a -54022 (kCommOptionPartSuccess) to indicate that there were changes from what was requested. In this case however, if for some reason the requested settings cannot be agreed on, the result will indicate a failure with a -54021 (kCommOptionFailure) value after the endpoint method which uses this option.

Other possible values of opCode are opGetDefault which uses the default settings for the device and opGetCurrent which uses the current settings.

The template form for the data slot essentially allows a C language structure to be passed from NewtonScript to the low-level comm tool we are connecting with. It can also be used to get data from the tool in certain cases (see the Options section below). A template (as shown in the above example), is a frame with two slots, an arglist slot which is an array of values, and a typelist slot which is an array of symbols defining the value types for each member of the structure. The first member of the typelist is always a 'struct symbol followed by any of several other type symbols including 'long, 'ulong, 'short, 'byte, 'char, 'unicodechar, 'boolean, 'struct and 'array. These are discussed at length in NPG: Communications on pp. 4-8 to 4-9.

A configOption frame may also have a type of 'service in which case it is specifying the low-level tool we wish to connect to. An example of this kind of option is shown here:

{ label: kCMSModemID,
type: 'service,
result: nil,
opCode: opSetRequired }


This options says that we want to connect to the modem tool and is obviously a required option since if we fail to get the requested tool, we can't proceed.

For more details on specific settings for different tools, see the article Endpoint Flavors or see the documentation in Chapter 5 of NPG: Communications together with the discussion of option setting on pp. 4-11 to 4-15. To see a complete configOptions array, see the Basic Modem Walkthrough Lab (or look at any of the DTS Sample code).

The encoding slot of the endpoint definition frame describes how all character data coming into and going out from the Newton is translated to and from Unicode. By default (and the usually desired case) we get the kMacRomanEncoding. This will map character values according to a translation table which matches ASCII character values with Unicode values using the Macintosh Roman mapping describe in the Macintosh Script Manager. Non-character data is not affected by the encoding slot. In the future it may be possible to define your own translation tables but for now we can only select those defined for the language systems delivered for the Newton.

The ExceptionHandler slot is discussed below in the Error Handling section.

In addition to the slots described above, it is common to have other, application specific slots added to the endpoint frame. These may include such communications related things as inputSpec frames or may simply be slots which the application wishes to get at when dealing with endpoint code. This last point is important as sometimes your endpoint code will be called when your application is not the currently running NewtonScript application (for example, when an idle script from another application is running). In this case, the endpoint frame may be the only thing available to you. For this reason many applications add a _parent slot to the endpoint frame and set it to the base view of the application at runtime so that the endpoint can "find" the application and all of the slots in it by virtue of parent inheritance. You will see examples of this in many places in this course and in much of the DTS sample code.

Parent inheritance is usually used in the view system.
Once the endpoint frame is defined, the next step in is to create a NewtonScript object for the endpoint. This is somewhat different from what a lot of Newton programmers are used to as the Newton view system automatically creates objects for views when they are opened. Since an endpoint is not created via the view system we must call the Instantiate method ourselves to create the object.

One of the things that happens when an endpoint object is instantiated is that the low-level Communications Manager launches a separate task for the appropriate communications tool. This task will interface with the endpoint object during its entire life cycle.

An example of code to instantiate the endpoint is shown below:

ep:Instantiate(ep, fEndPointOptions);

The first argument is the endpoint being instantiated, the second argument is an array of options. These are any options which are not in the definition but which are desired. This is often nil since in many cases there are no additional options we want to pass in to the low-level tool. An example of when we might use this capability is if we connect with different options at different times. In this case we might define an endpoint with the options in common (such as connection, speed, etc.) and, depending on different circumstances we might add other options when we instantiate the endpoint. For example, in one case we might make our Newton an AppleTalk server while in another we might make it a client. In this case we might change the size of the input buffer as we expect different traffic loads for the different cases.

Instantiation (unlike the other methods which follow) must be done synchronously. If the attempt to instantiate the endpoint fails you should of course make sure that you do not try to continue on and call the Bind method. This is a general truth about endpoints and indeed communications programming in general: you must always check for (and be prepared to handle) errors since at any time something beyond the control of your program may cause an error. (This is affectionately know as the Dog-knocked-the-plug-out-of-the-wall syndrome.)

Assuming the instantiation succeeds, the next step is to bind the endpoint. Binding causes the low-level tool to "warm up" the hardware which will be used. That is, it connects the Communications Manager task with the hardware it will be using and makes sure the Newton side of the communication link is ready to go.

A call to the Bind method looks like this:
ep:Bind(nil, nil);
The first argument is an options array as discussed in defining the endpoint, the second is a callback frame. If the Bind call is made asynchronously, then a callback frame must be defined which describes such things as timeout for the call and a method which is called on completion of the bind attempt. We will talk more about callback frames in the section on connecting as most calls to Bind are made synchronously by passing a nil value as its second argument.

Again, we must be careful that if the attempt to bind the endpoint fails then we must handle the error appropriately. One of the things is usually done in this case is to destroy the endpoint object as if we were unable to bind the endpoint to the hardware we probably can't use it and so we probably want to release the memory used by the endpoint object.

Because it is usually desirable to "undo" all steps in the process if there is an error, it is not unusual to define a series of constants which describe where we are in the process of connecting (or disconnecting) the endpoint so we can take appropriate action if there is an error. This can also be expanded to include states of any external protocol (e.g., a sign-on protocol) we may be using when communicating with an external device. An example of this "state machine" approach may be seen in the Basic Modem Walkthrough Lab or in the Asynchronous Endpoint Lab and the article Communications State Machines describes their use in more detail. An example of this would be to use an array of symbols describing the state the endpoint is currently in as follows:

vStates:=['Disconnected, 'Connecting, 'Connected, 'Logon, 'Data, 'Disconnecting];

Our current state could then simply be a subscript into this array and could be kept in a slot in our base view or in the endpoint frame.

Note that unlike the 1.x system you do not have to destroy the endpoint before trying to bind again.

Typical code in instantiating and binding an endpoint might look this:


try
ep:Instantiate(ep, ep.configOptions);
onexception |evt.ex.comm| do
begin
:MNotifyError(CurrentException().error);
return;
end;


try
ep:Bind(nil, nil);
onexception |evt.ex.comm| do
begin
:MNotifyError(CurrentException().error);
ep:Dispose();
return;
end;


Note the use of local exception handling (try blocks) to catch any errors thrown by the attempt to instantiate and bind the endpoint. For more on exception handling, see the section on Error Handling below. For another example of this kind of code see any of the DTS endpoint samples or any of the labs in this section of the course.

After an endpoint is bound it is ready to be connected to the external hardware. This may be done using the Connect method if the Newton is the initiator of the connection or Listen if it is waiting to be connected to. An example of when you would use Connect would be if you were connecting to a bulletin board or other mail system. Listen would be used when you were allowing a remote dial-in to your Newton or when the Newton was acting as a network server. In addition since the Newton infrared hardware is half-duplex (that is, at any given time it can only send or receive, not both) to connect two Newtons by an IR link on Newton must call Connect while the other calls Listen. For more details on this sort of difference see the article Introduction To Telecommunications.

As with the Bind method, these methods have two arguments: an option array and a callback spec. It is not unusual to have an address option passed in for things such as AppleTalk connections. It is also common for these calls to be made asynchronously as connection may take some time. It is important to understand what is meant by asynchronous calls. An asynchronous call is one where the effect of the call occurs independently from the calling program. That is, when you make an asynchronous call to an endpoint method, the request is sent to the low-level Communication Manager task but the NewtonScript task does not wait for a response before returning control to the caller. In a synchronous call the NewtonScript task would wait until the request (such as a connection request) was completed before returning the result to the NewtonScript calling application.

When talking about asynchronous calls then there are three phases:
1. When the application makes the asynchronous request
2. When the low-level Communications Manager task responds to the initial request and control returns to the NewtonScript caller
3. When the request is completed
Note that the Communications Manager task may return an error immediately after receiving the initial request (for example if a connect request was made on an unbound endpoint or if there was a memory problem). On the other hand, if it returns with no error, it does not mean that the request was successfully completed, instead we must wait until the end of the third phase before we can tell if the request was successful.

A callback spec frame has the following slots:
async - a boolean which describes whether the call should be asynchronous or not
reqTimeout - an integer describing (in milliseconds) how long to wait for the completion of a request after which a timeout error will be generated
CompletionScript - a closure which is called when the request completes regardless of whether the call was successful or not
The form of the CompletionScript is as follows:
CompletionScript(ep, options, result)
The ep parameter is a reference to the endpoint involved in the original call.

The options parameter is the option state of the endpoint. This allows the caller to see what the actual values of the options are if a negotiable option was passed into the call. For example, if you made an asynchronous request to connect to a service at 19,200 bps and the external device in question was only able to connect at 9600 bps, then the caller could see this change in the completion script by checking the options parameter.

The result parameter is an error code. If the request completed without error the value of result is nil.

When we say that CompletionScript is a closure, what we mean here is that it is a routine defined independently of any object. It does not belong to any object but is called directly from within the operating system. What this means is that it will not be running within your application. For this reason if you want to call a method from you application or set a slot, you must find it explicitly. This is why we suggested adding a _parent slot to your endpoint definition in the Defining Endpoints section above and defining it as a reference to your application base view. If the _parent slot is defined you can easily set a slot or call a method in the base view of the application by using the endpoint argument as follows:

ep.someSlot := 5; // parent inheritance will find it
ep:someMethod(result); // this too!

In fact, once the _parent slot is defined to refer to the application base view (or whatever view the desired slot and method are in) it is unnecessary to explicitly use ep to get them as the value of self (the currently active object) is the endpoint itself so the following code works just as well:

someSlot := 5; // parent inheritance will find it
:someMethod(result); // this too!

because if it can't find it in the endpoint frame it will look in the _proto chain and then the _parent chain. For more on closures and closure contexts and the value of self, see pp. 4-9 to 4-12 of The NewtonScript Programming Language which is available at the Newton Technical Documents web page.

The following code is taken from the solution code for the Converting 1.x to 2.0 Lab and shows an asynchronous call to Connect:

try
ep:Connect( [ address ],
{ async: true,
reqTimeout: 45000, // 45 seconds
completionScript: func(ep, options, result)
ep:MConnectCompProc(options,result),});
onexception |evt.ex.comm| do
begin
:MNotifyError(CurrentException().error);
:MDisconnect();
end;


Note the address option in the call to connect which provides the AppleTalk address for the device we want to connect to. The completionScript function is a single line long and calls a method defined in our base view called MConnectCompProc.

Part of the completion routine for this call is shown below:

func(options, result) //this function gets called after
// the Newton has successfully connected.
begin
if result then
begin
:MNotifyError(result);
:MDisconnect();
return;
end;
// more stuff follows for a successful connection
end


The Listen method is called in much the same way as Connect though there is an extra step. When you are waiting for a connection and a request to connect comes in you have the option of declining the connection by calling the Disconnect method (see the section Closing Endpoints for details on Disconnect). If on the other hand you want to accept the incoming connection you call the method Accept. Accept has the (familiar) format shown below:

Accept(option, callbackFrame)

where option is again a list of options describing the connection and callbackFrame is a callback spec.

An example of the use of Accept/Disconnect would be if you programmed the Newton to listen for AppleTalk connections and then, when a connection request arrived, if the connection was in your local zone, you would call Accept while if it was not you would call Disconnect .

The last thing to talk about in the connect phase is a series of utility routines which make the construction of key options much simpler. For the AppleTalk endpoint a routine called MakeAppleTalkOption takes an NBP address string and converts to an option frame suitable for use when connecting. This allows you to put up a chooser to allow the user to select the name of the device he or she wants to connect to and then take the name string and easily convert it to the format used for endpoint options.

Similarly the MakeModemOption call creates the kCMOModemDialing option for modem endpoints based on the user preferences stored in the system soup.

Finally the MakePhoneOption takes a telephone number as a string and converts it to an address option for connecting via a telephone.

Specifics on the options for each of these kinds of endpoints may be found in the Endpoint Flavors article. The user preferences in the system soup is described in NPG: System Software 2.0 on pp. 18-33 to 18-34 (First Edition).
Sending data is fairly straightforward on the Newton once a connection is made but there are several options which must be discussed.

To begin with, outputting data is often simply a matter of making a synchronous call to the endpoint method Output as follows:

ep:Output(theData,nil,nil);

The first argument is whatever NewtonScript data you want to send out. The second argument is an option array and the third argument is a kind of callback spec called and output spec. The options array is often ignored but can be used to turn on different features, say on a modem, while sending. The output spec will be described in detail below but for now we will simply comment that in addition to the asynchronous control provided by a callback spec there is additional control over how the data is translated during output.

We can make this simple call to Output in many cases because NewtonScript objects carry data type information in them and there are a set of defaults controlling how the data is formatted which will be exactly what we want to have happen.

However, if we want to control more exactly how the output is formatted, the output spec frame has the following slots (in addition to the async, reqTimeout and CompletionScript slots described above in the Opening Endpoints section):

sendFlags - flags which mark when a "packet" of data
form
- defines how outgoing data is translated
target
- used to break binary data into pieces for output

The sendFlags slot is used to "quantize" data. That is, it is used to group data being sent together. The kPacket constant indicates that the data being sent is part of a "packet." If the constant kMore is added in, it indicates that there is more data to be sent in the packet in later calls to Output. If the constant kEOP is added to kPacket it indicates that this is the final piece of data in the packet.

Here packet does not mean a network protocol packet.

This can be used to output things such as database transactions which are a series of actions or data which must all be applied to the database at the same time before unlocking it for another user. As we will see in the section on Receiving Data, we can use this packetization as a trigger for incoming data.

The form slot describes how the data being sent is to be formatted. The choices for the form slot are described in detail on pp. 4-6 to 4-7 of NPG: Communications but we will give a brief description of the different forms here.

If you are exchanging data between Newtons or between a Newton and a desktop machine using the Frame Desktop Integration Library (FDIL), the 'frame form allows NewtonScript frames to be flattened into a stream of bytes before being sent. For more on the FDIL, see the article DIL Overview.

There is also a utility which can be used to flatten data in memory.

To handle binary data such as pictures, sounds or such data objects, the 'binary form allows the data to be moved "as is" with no changes or translations. An example of code to send binary data will be shown below.

Integers may be sent using the 'number form but be aware that the since the lower 2-bits of NewtonScript integers are used to describe the data type, only the upper 30-bits are sent.

Character data may be sent using either the 'character form for single characters or the 'string form for NewtonScript strings. In either case the encoding slot in the endpoint frame determines how the system will handle the Unicode to ASCII character mapping (see the discussion of the encoding slot in the section Defining Endpoints). Note that the terminating character (usually a zero byte - $00) is neither sent nor received for strings.

The 'bytes form may be used to send individual values unchanged but be aware that the data will be truncated to fit into a byte. For example an 30-bit integer will be truncated so that only the last 8-bits will be sent. This may be useful if you have byte oriented data which has zero values in it.

Finally you may also send data out in a 'template form which maps values into a C structure format. The template form is describing above in the section Defining Endpoint where the option frame form is described.

Note that unlike 'character, 'string, 'integer and 'binary forms, the 'bytes and 'frames forms must be explicitly set for outgoing data. In other words, while the first group will be the defaults used by the endpoint system for NewtonScript objects of the defined type, 'bytes and 'frame must be explicitly set in the form slot of the output spec frame.

An example of the use of an output spec follows:

fEPOutputSpec := { form: 'frame, async: true,
completionScript:
func(ep, options, result)
ep:MOutputCompProc(options, result),
}

ep:Output(aFrame,nil,fEPOutputSpec);
Here we are sending a frame and the output will be done asynchronously with a callback routine which calls our MOutputCompProc.

Finally the target slot is available to allow sending large binary objects out a piece at a time without making copies of the piece being sent. The target slot is a frame which describes a binary object and a place marker within that object where we left off sending. The offset slot keeps track of where in the object we left off sending, the length slot describes how much data should be sent in each call to Output. An example of this is shown below:

OutputBinary := func( binary, chunkSize )
begin
local binLength := Length( binary );

ep.chunkSize := chunkSize;
ep.sendingOffset := 0;
ep.sendingData := binary;
ep:Output( ep.sendingData, nil, {_proto: ep.OutSpec,
form: 'binary,
target: {offset: p.sendingOffset,
length: ep.chunkSize}} );
end

ep.OutSpec :=
{
async: true,
CompletionScript: func( ep, options, result )
begin
if ep.sendingOffset + ep.chunkSize >=
Length( ep.sendingData ) then
ep.chunkSize = Length(ep.sendingData)-
ep.sendingOffset;

if (ep.chunkSize<>0) then
begin
ep.sendingOffset:=ep.sendingOffset+ep.chunkSize;
ep:Output( ep.sendingData, nil,
{_proto: ep.OutputSpecification,
form: 'binary,
target: {offset: ep.sendingOffset,
length: ep.chunkSize}} );
end;
end;
}


In this example we have a method OutputBinary which calls Output asynchronously using an output spec which has a target slot set for binary output. In this case we are proto'ing from the frame OutSpec defined below as we will need to update the target frame periodically in order to reset the point within the binary object we want to output the next chunk from. By using prototype inheritance we overcome the problem of trying to change a slot in a template (see the discussion of creating endpoint frames in the Simple Endpoint Lab). In the CompletionScript we update the sendingOffset before calling the Output method again.

This technique of calling Output from the completion script of a previous call to Output is called call chaining and is a common way to make successive asynchronous calls. Note that we must take special care that the final call to Output has a chunkSize set to the number of bytes remaining in the binary object. And, since chunkSize will become zero on the next call to the completion routine, Output will not be called again and the call chain will be broken completing the output process.

The primary mechanism for receiving data is the inputSpec frame. It can be used in a number of different ways to control the formatting of incoming data and to control how data is received by our application. If you have not done so, it is probably worth looking at the Input Spec Animation to get a high-level view of the kind of control inputSpec frames give us. This section will describe this control in a code-specific way while the animation gives a conceptual view of the process.
The simplest (and most common) use of an inputSpec is to provide a termination condition. A termination condition describes input conditions which will cause one of our methods to be called by the endpoint system. An example would be looking for the string "Login" or looking for a carriage return character or both at the same time. This is done by using the termination slot of an input spec in conjunction with an InputScript method. These are both part of an inputSpec definition and might look like this:

termination: {endSequence: [unicodeCR],},

InputScript: func(endpoint, data, terminator, options)
begin
endpoint:MInput(data);
end,


This example defines a termination frame which has an end sequence of a carriage return character. The endSequence is an array of values, any of which will trigger a call to the InputScript. These values may be single characters, strings, numbers or an array of bytes. In this case all that happens is that when a carriage return arrives in the input stream, the contents of the input buffer are passed to the InputScript which simply calls a method to handle incoming data.

The termination frame can also have a byteCount slot which specifies the number of bytes of input we want to receive before having the InputScript called. Note that byteCount is not exclusive of an endSequence. In other words, we could change the termination frame so that it looked like this:

termination:{endSequence:[unicodeCR],byteCount:100},

in which case the system would take up to 100 characters of input while looking for a carriage return. If no carriage return character appeared, then it would call InputScript with whatever characters had arrived.

The last termination option is the useEOP slot. This tells the system to look for an end-of-packet marker described in the Sending Data section above. If the end-of-packet marker arrives then all of the data in the packet is sent to InputScript. As with the byteCount condition, this may be used in conjunction with the other termination conditions though if you are concerned with packetized data you would probably try not to trigger on another condition.

Note that the call to InputScript has the following arguments:

endpoint - this is the endpoint frame
data - the data in the input buffer when the termination condition occurred
terminator - a description of what caused InputScript to be called, this is a frame
with the following slots:
condition - a symbol describing the cause (e.g., 'byteCount)
index - an index into the endSequence array to show which
sequence cause the call
byteCount - the number of bytes passed to InputScript
options - an array of option frames for the endpoint set by the inputSpec
(described below)


In addition to the termination and InputScript slots, an inputSpec frame has the following slots:


form - describes how the incoming data is to be formatted, this is about the same as
the form slot in an outputSpec as described in the section Sending Data
except that there is no automatic formatting done since incoming data does
not contain type information
target - this is used for receiving binary data or templates for details of how it is
used, see pp. 4-40 to 4-41 of NPG: Communications
discardAfter - describes the input buffer size. This controls how many bytes of
input should be saved. Any bytes received in excess of this
amount are thrown out. The default value is 1024.
rcvFlags - this should be kPacket if packetized data is expected, otherwise it
should be set to nil or simply omitted from the inputSpec
reqTimeout - the length of time in milliseconds to wait for input. If time expires
before input is received, CompletionScript is called.
CompletionScript - method called if there is an abnormal termination of an
inputSpec (e.g., because of a timeout)
filter - a frame which can be used to filter incoming data by stripping the high bit or by providing a translation for one byte to another (e.g., linefeed character to carriage return). The filter frame is described in detail on pp. 4-42 to 4-43 of NPG: Communications.
rcvOptions - an option frame array used to set endpoint options for input when
the inputSpec is used. The options are only applied at the time the
inputSpec is set (see description of setting inputSpecs below).
partialFrequency - an amount of time in milliseconds after which the
PartialScript is called. This is often used to check for
new input as calls to PartialScript do not remove data
from the input buffer.
PartialScript - method called every partialFrequency milliseconds


The format for the CompletionScript and PartialScript are as follows:

CompletionScript(ep, options, result)
PartialScript(ep, data)

where ep is the endpoint, options an option frame array, data the contents of the input buffer at the time of the call and result is a result code. Note (as mentioned above) that a call to PartialScript does not remove the data from the input buffer so that we can still use a termination condition even while we are reviewing (and displaying) the input.

A more complete inputSpec than the one shown above might look like this:

inputSpec := {
form: 'string,
termination: { endSequence:[unicodeCR,"Logout"], },
discardAfter: 256,
inputScript: func(ep, data, terminator, options)
ep:MInput(data),
completionScript: func(ep, options, result)
ep:MNotifyError(result),
}


It is typical of an inputSpec that it use only a few of the slots described above.

If there is one inputSpec which is used for the entire life of the endpoint it is not unusual for it to be defined in the endpoint frame itself. If there are several inputSpecs (say to deal with a bulletin board system), they are usually defined in the base view of the application simply for ease of reading.

Defining an inputSpec is only half of the job. To put the inputSpec into play you must call the endpoint method SetInputSpec as follows:

ep:SetInputSpec(inputSpec)

This call makes the inputSpec passed in the currently active one controlling input. If you are switching between inputSpecs you must first call the Cancel method before setting a new inputSpec. This is done as follows:

ep:Cancel(); // out with the old
ep:SetInputSpec(newSpec); // in with the new

Finally, the first call to SetInputSpec will typically be done after the endpoint is connected, either in the routine which calls Connect if it is a synchronous call or in the callback routine if the Connect call was made asynchronously. The State Machine Article describes using inputSpecs to create a finite state machine for handling communications.

In addition to the inputScript methods InputScript, CompletionScript and PartialScript, there are some endpoint methods which affect the input buffer as follows:

ep:Partial()- uses inputSpec formatting to get a copy of the input buffer
ep:FlushPartial() - clears all data in the buffer since the last Partial call
ep:FlushInput() - clears all data in the input buffer
ep:Input() - terminates the current inputSpec and causes InputScript to be called with the current contents of the input buffer

Closing endpoints involves disconnecting the endpoint, unbinding it and destroying the NewtonScript endpoint object. While it is not always necessary (or preferable) to do all of these if you merely want to disconnect temporarily, in many cases you will want to completely destroy the endpoint in order to free up the memory it uses. We will look at each of these steps one at a time.

Disconnecting terminates the connection to the external device. This is done with a call to the Disconnect method which has the following format:

ep:Disconnect(cancelRequests, callback)

The first argument is a Boolean describing whether or not to cancel all outstanding requests. If it is true, requests are canceled, if nil, they are not and the disconnect will not take place until the pending requests are completed.

The second argument is a callbackSpec frame (see the section on Opening Endpoints for details on callbackSpecs) used for asynchronous disconnects. If the second argument is nil, the call is synchronous. It is common for Disconnect calls to be asynchronous both to make sure the low-level tool has finished cleaning up after a disconnect call before closing the tool and because (as with Connect calls) the disconnect process may take some time to complete and it is probably desirable to not lock the user out from other activity.

Once the connection is broken it is usual to proceed to unbinding the endpoint from the low-level tool. This is done with the Unbind method as follows:

ep:Unbind(callback)

Here the only argument is a callbackSpec if you wish to make the call asynchronous. The Unbind call kills the low-level Communications Manager task and closes the low-level tool instance.

If unbinding is successful, the next step is usually to destroy the endpoint by calling the Dispose method as follows:

ep:Dispose()

This destroys the endpoint object created when the endpoint was instantiated.

You may want to delete the reference to the endpoint frame to save memory.

Putting these calls together we might have the following code for closing an endpoint:

ep:Disconnect(nil, nil);
try
ep:UnBind(nil)
onexception |evt.ex.comm| do
nil;

try
ep:Dispose()
onexception |evt.ex.comm| do
nil;


Note that the try blocks above do nothing if there is an error. If you're not going to do anything, why check for an error? Because otherwise the endpoint exception handler (or the global exception handler) would be called and frankly, if the calls to Unbind and Dispose fail, there's nothing much we can do about it and notifying the user probably won't do anything other than confuse him or her.

As mentioned previously, it is extremely important to check carefully for errors when writing communications code as so much can go wrong. In the Newton there are three layers of error handling available when writing communications code beyond simply checking return values.

The first level is the use of local try-blocks to catch errors thrown by the system. A try-block is a section of code which is bracketed by the keywords try...onexception...do... For example the following code is useful for checking attempts to bind an endpoint:

try
ep:Bind(nil, nil);
onexception |evt.ex.comm| do
begin
:MNotifyError(CurrentException().error);
ep:Dispose();
return;
end;


The code between the try statement and the onexception statement (here ep:Bind(nil,nil)) is specified as the try-block. If an error is thrown by the system (i.e., if the code would normally cause the system to put up an error slip), and if it is of the class of errors specified in the onexception statement (|evt.ex.comm| - a communications error), then the block of code following the do is executed. In this case we pass the error number to our generic error display routine, dispose of the endpoint object and return to the caller. While this example has only one line of code in the try-block (the Bind call), there could be as many lines of code as you want within the block. If any of them caused a communications exception, our error handling code would be run.

Note that the difference between try-blocks and just checking error returns is that try-blocks will catch errors which would normally interrupt execution. For example, the code above will catch an error if you try to bind and endpoint to a port which is already open for another application. If there was no try-block or other error handling, the system would put up an error-slip with a simple error message and would continue running our application which would be in an unknown (wrong) state which might very well cause other even more deadly errors. Instead, we can recover from the problem, put up an error message particularly to our application and can leave our endpoint in a known (unconnected) state ready for the user to try again once the problem has been cleared up.

Note that try-blocks may be defined at different levels of an application so that the call to the routine which attempts to bind the endpoint may lie within a try-block higher up the application stack. If the bind fails therefore and the higher level try-block will catch communications errors, the higher level error handling code will be called. Note also that you can have different blocks of code for different types of errors. In other words, the above example could be expanded as follows:

try
ep:Bind(nil, nil);
onexception |evt.ex.comm| do
begin
:MNotifyError(CurrentException().error);
ep:Dispose();
return;
end;
onexception |evt.ex.pgm| // program error
begin
// do something else
end;


For more on try-blocks and general NewtonScript error handling, see the section on Exception Handling beginning on pp. 3-13 of The NewtonScript Programming Language which is available from the Newton technical documentation web page.

The next level of error handling for communications programming is a CompletionScript. As described in the section on Receiving Data, an inputSpec may have a CompletionScript defined for cases when something goes wrong with an endpoint (say connection is lost) while the inputSpec is active. In this case the CompletionScript is called with the appropriate error code as an argument.

The next level of error handling for communications programming is the endpoint exception handler. You may recall that in the section Defining Endpoints we mentioned the ExceptionHandler method slot. This is a method which you may define which is designed to catch any errors which occur when an endpoint is involved and there is neither a try-block nor a CompletionScript active to handle the problem. The ExceptionHandler has the following format:

ExceptionHandler(errorFrame)

where the errorFrame has the following slots:
name - string describing the exception (e.g., |evt.ex.comm|)
data
- error code number for the error
debug
- symbol describing failure of callback
The name and the data slots are fairly straightforward. Table 9 on pp. 4-61 of NPG: Communications gives a list of the common communications errors. Appendix A of NPG: System Software details the exception strings.

The debug slot of the error frame is used to indicate that the exception occurred in a callback routine. In such as case the slot will have one of the following symbols:
'inputscript
'completionscript
'eventhandler
'partialscript
An example of when this might occur is if you defined an InputScript method as having too many or too few arguments. In this case the exception would occur only when the method was called but since it was called at a time when endpoint code could not be counted on to be running, the debug slot of the error frame tells you where the error occurred.

The EventHandler method is described in the Options section below.

Note that these three error handlers, try-blocks, CompletionScript and ErrorHandler methods are interwoven. For example, if a local try-block exists it is used in preference to any other error handling scheme. However, if it does not exist or does not handle the relevant type of error the ErrorHandler method will be called. Similarly if an error occurs for an inputScript and a CompletionScript occurs, it will be called. However if there is no CompletionScript or if it throws an error, then the ErrorHandler method for the endpoint will be called. Finally, if none of these schemes handle the error, (and assuming there are no other try-blocks defined higher up in the application call chain), the global system error handler will be called and will put up a simple error slip describing the error.

Because active communications uses a lot of power, and because the Newton is designed to minimize power consumption, when the Newton goes to sleep it attempts to terminate all open connections. However, since in some cases this is clearly not desirable, the Newton first notifies all applications which have registered as requiring notification that the Newton wants to go to sleep. This gives these registered applications time to do one of three things:
1. Nothing, in which case the Newton continues its sleep process and if the application has an open endpoint, the connection will be terminated.

2. Tell the Newton not to sleep. In which case the Newton will not go to sleep. This may be somewhat surprising to the user as he or she will be unable to "turn off" the Newton by pressing the power switch.

3. Tell the Newton that it is in the process of shutting down and to come back in a while. The application can then gracefully close the connection, notifying the user if desired and putting the application (and the endpoint) into a known state (disconnected).
To register a power off handler routine we make the following call:
RegPowerOff(kAppSymbol, MyPowerOffHandler)
The first argument is your application symbol and tells the system that you application wants to be notified when there is an attempt to go to sleep. For more details on your application symbol, see pp. 2-10 of NPG: System Software. The second argument is a closure which will be called when the machine is attempting to go to sleep. The power off handler routine has the following arguments (of course the names don't matter):
MyPowerOffHandler(what, why)
The what parameter describes where in the power off sequence we are and the why parameter describes the cause of the power off sequence. When a power off begins, the system calls the power off handlers of all programs which registered a handler. At this time the value of what will be the symbol 'okToPowerOff. If an application is willing to accept a power off, it should return a value true which tells the system to proceed with the power off sequence. A return of nil cancels the shutdown sequence (i.e., the Newton will not power down).

Of course it is not really powering off.

If all applications with registered power off handlers return true from this first call, the sequence continues and the system will call each power off handler again, this time with a value of 'powerOff for the what parameter. If the handler wants to take some action which will take time (such as an asynchronous call to disconnect the endpoint), it should return the symbol 'holdYourHorses which stops the power off sequence until the application calls the function PowerOffResume. If the handler instead returns a nil, then the power off sequence continues and, assuming no other application delays the shutdown, the Newton goes to sleep.

During each call to the power off handler the why parameter describes the reason for the power off sequence. There are three possible values which can be received for the why parameter: the symbol 'user which indicates the user pressed the power off switch; the symbol 'idle which indicates that the Newton had been idle for too long (were too long is defined as the amount of time set in the user preference for sleep); and the symbol 'because which indicates the Newton is going to sleep for unknown reasons.

From this you can see that there are several possible responses to a request to power off. Here are some of the common ones:
1. Refuse all power off requests while your application is running. This is what the NTK Inspector does while it is connected to the desktop machine.

2. Refuse all power off requests if why is 'idle but accept it if is 'user or 'because. This then has two sub-branches: a. disconnect (or destroy) the endpoint immediately when the 'powerOff call comes in ; b. return 'holdyourhorses after beginning an asynchronous disconnect (or destruction) sequence.
Note that we do not begin a disconnect after an 'okToPowerOff call of the power off handler since another application may terminate the power off sequence for its own reasons and then we will be disconnected and the Newton will still be awake.

Normally we register a power off handler after we have successfully connected the endpoint and we unregister it after we have disconnected. We unregister it by the following call:
UnRegPowerOff(kAppSymbol);
Here is an example of the power off handler from the DTS Sample, Basic Modem:

RegPowerOff(kAppSymbol,
func(what, why)
// we create the closure here so as to set up SELF as the // endpoint frame in the closure
begin
if what = 'okToPowerOff then
begin
if why <> 'idle // keep the unit awake
// whenever we're connected
or fEndPointState = kState_Disconnected
then // unless the user or an
// application explicitly
return true; // wants it to sleep
end;
else if what = 'powerOff then
begin
if why <> 'idle // if we simply must go
// to sleep but we're still
and fEndPointState <> kState_Disconnected
then // connected begin disconnect
begin
fPowerOffState := 'holdYourHorses;
// set flag that we're powering down
:MDisconnect();
return 'holdYourHorses;
end;
end;

nil; // ALWAYS return nil here!
end );


When the initial call to the power off handler is made, it checks to see why it was called. If it was because the Newton timed out and wanted to sleep, then it will simply return nil, ignoring the sleep request. If it is because of a user shut down attempt or any other reason it will allow the shutdown to proceed and when it is called again the handler will begin an asynchronous disconnect.

There are two ways that a NewtonScript endpoint can communicate directly with a low-level communications tool: the Option method and the EventHandler method. The Option method is used to send information to the communications tool, the EventHandler to receive information sent by the tool. We will look at these methods one at a time.

The Option method is used to send option frames to the low-level tool. These may be standard option frames described in Chapters 4 and 5 of NPG: Communications or they may be manufacture specific option frames.

This is basically the same thing.

As we have seen option frames may be passed to a communications tool at any of several points during the life cycle of the endpoint. Some, such as the definition of the service or the address being used during a connect, must be available at the time certain methods are called. Others may be sent whenever you want to change a setting of the tool (e.g., changing the speed or error correction settings for a particular call to Output).

The Option method is used when you want to send information to a low-level communications tool other than from the methods described previously. An example of this might be if you had a PCMCIA modem card which had a particular control set (such as an extended Hayes command set) and the communications tool for the card allowed you to send these to the card. In this case you would use the Option call to send a command to the card.

The Option call looks like this:

ep:Option(options, callbackFrame)

The options argument is an array of option frames as described in the section Defining Endpoints. The callbackFrame argument is a callback spec frame as described on pp. 4-35 of NPG: Communications as well as in the Opening Endpoints section above. Note that a low-level communications tool can use the options and result arguments of the callback method to return information in response to an option call. In other words, the options array of the callback may have a response to an option frame. The result may have a value returned by the tool. For example, if you sent an option to a modem tool requesting the value of a register (ATSn? for a Hayes compatible modem), you might get the contents back either in the third argument of the CompletionScript (the result argument) or as a slot in an option frame in the second argument (the options argument).

The EventHandler works in the opposite way in that it allows the low-level tool to send a message (or more exactly, an event frame) to an endpoint. Again, if you are going to have an EventHandler method to receive events you must know what events to expect from a given tool.

The EventHandler method looks is called by the tool and looks like this:

EventHandler(event)

where event is a frame with the following slots:

eventCode - integer value describing the event (you must know what each value
means)
data
- an integer for any data associated with the event
serviceID
- string describing the tool sending the event (e.g., "mods" for the
modem tool)
time
- ticks since the system was restarted when the event occurred


An example of the use of the EventHandler would be a wireless modem reporting on a change in signal strength. In this case the eventCode parameter might be a value indicating that there was a change and the data parameter might have the current strength.
This article is the heart of the endpoint section. For more specific details about endpoints, try the articles Communications State Machines, Endpoint Flavors and Endpoint Data Forms. For a more general overview see the section on endpoints in the article 2.0 Communications Overview. The article Debugging Endpoints talks gives some tips on how to get endpoint code running correctly.

To see some example code, you might try the Basic Modem Walkthrough Lab which goes step-by-step through the communications code in this DTS sample. To try your hand at creating an endpoint, try Simple Endpoint Lab. For a more elaborate example, try the Asynchronous Endpoint Lab.

The Input Spec Animation is a QuickTime movie which demonstrates the inputSpec mechanism and is strongly recommended as a way to get "the big picture" regarding inputSpecs.

As a reference and source for detailed documentation, see Chapters 4 and 5 of NPG: Communications which is available from the Newton Documentation site.

Currently all NewtonScript applications run in a single thread on the Newton and share the control of the thread through cooperative multi-tasking. At some point in the future this model may change so we will always talk about NewtonScript applications as if they are running in separate thread.



The _parent slot is used primarily by the view system to represent containment relationships. That is, the base view is the parent of the child sub-views because it contains them. This allows the base view to hold all slots and methods which are shared among descendent views. In the case of an endpoint frame we are using this behavior to allow the endpoint frame to reference any slot or method which is the base view even though the endpoint has no visual representation in the application. For more on parent inheritance, see The NewtonScript Programming Language, pp. 5-4 and 5-5.



In the 1.x system if Bind (or Connect) fail, you must dispose of it before you can try again. This is not true in the 2.0 system.



By packet what is meant is a group of data which belongs together. If a networking protocol (such as AppleTalk or TCP/IP) is being used, it will fill packets of the appropriate size and send them as necessary without paying any attention to the sendFlags slot. In other words, this is a high-level protocol rather than a transport layer protocol.



The Translate utility is designed to allow you to convert data to another format in memory. Currently the only two choices are to flatten a frame (convert it to a stream of bytes) and unflatten data (convert a stream of bytes into a NewtonScript frame). For details on Translate, see pp. 4-57 to 4-58 of NPG: Communications.



If you have defined a slot in your application base view which points to an endpoint frame defined in memory, then it will not be garbage collected since the base view is never really closed. So in the case we suggested, when defining the endpoint frame dynamically by returning a reference from a method call (MBaseEndpoint in the example in the Defining Endpoints section), the memory allocated and referred to will remain in use. To free this memory you simply have to assign a nil value to the slot in the base view used for the endpoint. Of course you may want to keep the definition around to speed future connections but you cannot rely on it being there if there is a machine reset as this clears all dynamic memory.



The Newton never really shuts its power off unless there is a hardware problem (like the batteries run flat). What actually happens is that it goes to sleep, that is it powers down everything (like communications connections) which consume power and reduces power to memory and the cpu to a minimum. This is why the Newton comes awake so fast when you press the "power" button; it's not really off so it doesn't have to reboot, it just "wakes up." Of course "waking up" does not reestablish endpoint connections so we must take the steps described in this section.



In essence all we are saying is that they are specific to the tool. The built-in tools shipped by Apple in the Newton are documented in NPG: Communications, the tool-specific options from for other tools should be described in their documentation. For example, the Newton Internet Enabler shipped with specific options which were described in the documentation for the NIE.



The EventHandler method is called when a low-level communications tool sends a message which is not understood by the endpoint system. In such circumstances if you have defined an EventHandler, it will be called.


Navigation graphic, see text links

Developer Services | Technical Information | Tools | Sample Code

Main | Page One | What's New | Apple Computer, Inc. | Find It | Contact Us | Help


Copyright Apple Computer, Inc.
Maintained online by commscourse@newton.apple.com
Updated 26-Sep-96 by dcd