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:
We'll look at these slots one at a time.
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
In the 2.0 system the most commonly used system prototype for endpoints isprotoBasicEndpoint
. It completely replaces the 1.x system prototypeprotoEndpoint
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 usingprotoEndpoint
will continue to run but cannot be re-compiled for the 2.0 platform.) There is also aprotoStreamingEndpoint
which is proto'ed fromprotoBasicEndpoint
though it is not frequently used. The slots listed above are those which are set by programmers usingprotoBasicEndpoint
.
TheconfigOptions
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 theconfigOptions
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
slot - is set by the call to an endpoint method and describes success or failure of the attempted call
result
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 aconfigOptions
array:In this example we are setting the I/O parameters for a serial connection. It is defined as being of type
{ 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
'option
(that is, it is a setting or other supplemental information) and theopCode
constant ofopSetRequired
says we must get exactly the settings requested to proceed. The data is in atemplate
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 theopCode
slot toopSetNegotiate
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 ofopCode
areopGetDefault
which uses the default settings for the device andopGetCurrent
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.
AconfigOption
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 completeconfigOptions
array, see the Basic Modem Walkthrough Lab (or look at any of the DTS Sample code).
Theencoding
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 thekMacRomanEncoding
. 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 theencoding
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.
TheExceptionHandler
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 asinputSpec
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 theInstantiate
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 theBind
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 theBind
method looks like this: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 toep:Bind(nil, nil);
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 theConnect
method if the Newton is the initiator of the connection orListen
if it is waiting to be connected to. An example of when you would useConnect
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 callConnect
while the other callsListen
. For more details on this sort of difference see the article Introduction To Telecommunications.
As with theBind
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:
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.
- 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
A callback spec frame has the following slots:
The form of the
async
- a boolean which describes whether the call should be asynchronous or notreqTimeout
- an integer describing (in milliseconds) how long to wait for the completion of a request after which a timeout error will be generatedCompletionScript
- a closure which is called when the request completes regardless of whether the call was successful or notCompletionScript
is as follows:TheCompletionScript(ep, options, result)
ep
parameter is a reference to the endpoint involved in the original call.
Theoptions
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.
Theresult
parameter is an error code. If the request completed without error the value ofresult
is nil.
When we say thatCompletionScript
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
In fact, once the
ep:someMethod(result); // this too!
_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 ofself
(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 toConnect
:
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. ThecompletionScript
function is a single line long and calls a method defined in our base view calledMConnectCompProc
.
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
TheListen
method is called in much the same way asConnect
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 theDisconnect
method (see the section Closing Endpoints for details onDisconnect
). If on the other hand you want to accept the incoming connection you call the methodAccept
.Accept
has the (familiar) format shown below:
Accept(option, callbackFrame)
where
option
is again a list of options describing the connection andcallbackFrame
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 callAccept
while if it was not you would callDisconnect
.
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 calledMakeAppleTalkOption
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 theMakeModemOption
call creates thekCMOModemDialing
option for modem endpoints based on the user preferences stored in the system soup.
Finally theMakePhoneOption
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 methodOutput
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 toOutput
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 theasync
,reqTimeout
andCompletionScript
slots described above in the Opening Endpoints section):
sendFlags
- flags which mark when a "packet" of data- defines how outgoing data is translated
form- used to break binary data into pieces for output
target
ThesendFlags
slot is used to "quantize" data. That is, it is used to group data being sent together. ThekPacket
constant indicates that the data being sent is part of a "packet." If the constantkMore
is added in, it indicates that there is more data to be sent in the packet in later calls toOutput
. If the constantkEOP
is added tokPacket
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.
Theform
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:
Here we are sending a frame and the output will be done asynchronously with a callback routine which calls our fEPOutputSpec := { form: 'frame, async: true,
completionScript:
func(ep, options, result)
ep:MOutputCompProc(options, result),
}
ep:Output(aFrame,nil,fEPOutputSpec);MOutputCompProc
.
Finally thetarget
slot is available to allow sending large binary objects out a piece at a time without making copies of the piece being sent. Thetarget
slot is a frame which describes a binary object and a place marker within that object where we left off sending. Theoffset
slot keeps track of where in the object we left off sending, thelength
slot describes how much data should be sent in each call toOutput.
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 methodOutputBinary
which callsOutput
asynchronously using an output spec which has atarget
slot set for binary output. In this case we are proto'ing from the frameOutSpec
defined below as we will need to update thetarget
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 theCompletionScript
we update thesendingOffset
before calling theOutput
method again.
This technique of callingOutput
from the completion script of a previous call toOutput
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 toOutput
has achunkSize
set to the number of bytes remaining in the binary object. And, sincechunkSize
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 thetermination
slot of an input spec in conjunction with anInputScript
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 atermination
frame which has an end sequence of a carriage return character. TheendSequence
is an array of values, any of which will trigger a call to theInputScript
. 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 theInputScript
which simply calls a method to handle incoming data.
Thetermination
frame can also have abyteCount
slot which specifies the number of bytes of input we want to receive before having theInputScript
called. Note thatbyteCount
is not exclusive of anendSequence
. 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 callInputScript
with whatever characters had arrived.
The last termination option is theuseEOP
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 toInputScript
. As with thebyteCount
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 toInputScript
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 theendSequence
array to show which
sequence cause the call
byteCount
- the number of bytes passed toInputScript
options
- an array of option frames for the endpoint set by theinputSpec
(described below)
In addition to the termination andInputScript
slots, an inputSpec frame has the following slots:
form
- describes how the incoming data is to be formatted, this is about the same as
theform
slot in anoutputSpec
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 bekPacket
if packetized data is expected, otherwise it
should be set tonil
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 toPartialScript
do not remove data
from the input buffer.
PartialScript
- method called everypartialFrequency
milliseconds
The format for the CompletionScript and PartialScript are as follows:
CompletionScript(ep, options, result)
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(ep, data)
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 methodSetInputSpec
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
Finally, the first call to
ep:SetInputSpec(newSpec); // in with the new
SetInputSpec
will typically be done after the endpoint is connected, either in the routine which callsConnect
if it is a synchronous call or in the callback routine if theConnect
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 methodsInputScript
,CompletionScript
andPartialScript
, 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 lastPartial
call
ep:FlushInput()
- clears all data in the input buffer
ep:Input()
- terminates the current inputSpec and causesInputScript
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 theDisconnect
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 forDisconnect
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 withConnect
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 theUnbind
method as follows:
ep:Unbind(callback)
Here the only argument is a callbackSpec if you wish to make the call asynchronous. TheUnbind
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 toUnbind
andDispose
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 keywordstry...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 thetry
statement and theonexception
statement (hereep: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 thedo
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:
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.
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;
The next level of error handling for communications programming is aCompletionScript
. As described in the section on Receiving Data, an inputSpec may have aCompletionScript
defined for cases when something goes wrong with an endpoint (say connection is lost) while the inputSpec is active. In this case theCompletionScript
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 theExceptionHandler
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 aCompletionScript
active to handle the problem. The ExceptionHandler has the following format:
ExceptionHandler(errorFrame)
where the
errorFrame
has the following slots:Thename
- string describing the exception (e.g.,|evt.ex.comm|
)- error code number for the error
data- symbol describing failure of callback
debugname
and thedata
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:An example of when this might occur is if you defined an'inputscript
'completionscript
'eventhandler
'partialscriptInputScript
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, thedebug
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
andErrorHandler
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 theErrorHandler
method will be called. Similarly if an error occurs for an inputScript and aCompletionScript
occurs, it will be called. However if there is noCompletionScript
or if it throws an error, then theErrorHandler
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.To register a power off handler routine we make the following call:
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).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):RegPowerOff(kAppSymbol, MyPowerOffHandler)
TheMyPowerOffHandler(what, why)
what
parameter describes where in the power off sequence we are and thewhy
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 ofwhat
will be the symbol'okToPowerOff.
If an application is willing to accept a power off, it should return a valuetrue
which tells the system to proceed with the power off sequence. A return ofnil
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 returntrue
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 thewhat
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 functionPowerOffResume.
If the handler instead returns anil,
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 thewhy
parameter describes the reason for the power off sequence. There are three possible values which can be received for thewhy
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.Note that we do not begin a disconnect after an
2. Refuse all power off requests ifwhy
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.'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:Here is an example of the power off handler from the DTS Sample, Basic Modem:UnRegPowerOff(kAppSymbol);
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: theOption
method and theEventHandler
method. TheOption
method is used to send information to the communications tool, theEventHandler
to receive information sent by the tool. We will look at these methods one at a time.
TheOption
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 toOutput
).
TheOption
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 theOption
call to send a command to the card.
TheOption
call looks like this:
ep:Option(options, callbackFrame)
The
options
argument is an array of option frames as described in the section Defining Endpoints. ThecallbackFrame
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 theCompletionScript
(the result argument) or as a slot in an option frame in the second argument (the options argument).
TheEventHandler
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 anEventHandler
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)- an integer for any data associated with the event
data- string describing the tool sending the event (e.g., "mods" for the
serviceID
modem tool)- ticks since the system was restarted when the event occurred
time
An example of the use of theEventHandler
would be a wireless modem reporting on a change in signal strength. In this case theeventCode
parameter might be a value indicating that there was a change and thedata
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.
Bind
(or Connect
) fail, you must dispose of
it before you can try again. This is not true in the 2.0 system.sendFlags
slot. In other words, this is a high-level
protocol rather than a transport layer protocol.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.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.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