TweetFollow Us on Twitter

Control Freaks Alert

Volume Number: 16 (2000)
Issue Number: 3
Column Tag: Data Acquisition on the Macintosh

Control Freaks Alert!

by Tom Djajadiningrat

A clean interface to Beehive Technologies' ADB I/O using object-oriented Lingo


The ADB I/O by Beehive Technologies is a device which you can connect to a Macintosh to communicate with external electronics. Simply put, the ADB I/O provides digital inputs (to read switches), digital outputs (to switch things on or off) and analog inputs with 8-bit resolution (to read all kinds of sensors). For the Macintosh platform, it has opened up areas of application to mainstream users that were previously the sole territory of electronic hobbyists or users with high end data acquisition hardware. A few of those areas are home automation, robotics, kiosks, scientific experiments and product simulation.

Many applications can communicate with the ADB I/O. One particularly powerful combination is the ADB I/O and Macromedia Director. Even if you intend to control your ADB I/O through AppleEvents or C/C++, you may find it worthwhile to test your set-up with Director. Director is easy to program, especially when it comes to the graphical user interface.

There is of course a downside to Director's ease of use: the temptation of sloppy programming. Director does not require you to declare local variables or provide function prototypes. Add Director's cryptic error messages and your program can turn into a tangled mess which is difficult to debug. While this is of course undesirable for any program, with a program that communicates with external devices, for example motors, the consequences of messy programming may be more severe.

This article aims to help you in battling messy Director code. By using Director's object-oriented features you will get to build a friendly and clean interface to the ADB I/O. In addition, in this article consistent naming for variables is used, which may be of help to you in writing your own scripts.

Required Hardware and Software

Required hardware:

  • A Macintosh with an ADB port or a Macintosh with a USB port and a Griffin Technology iMate USB to ADB adapter.
  • A Beehive Technologies' ADB I/O.

Required software:

  • ADB I/O XCMDs 1.0.1. This file comes with the ADB I/O. It is also downloadable from the Beehive Technologies website.
  • Macromedia Director 5 or 6 (Director 7 is not compatible with the ADB I/O XCMDs 1.01 file. A Director 7 compatible Xtra for the ADB I/O is currently in beta and downloadable from the Beehive Technologies website). Though this article explains things for Director 6, the differences with Director 5 are minimal and the sample files which accompany this article are provided in both Director 5 and 6 versions.

What You Need to Know

You need to be familiar with Macromedia Director and its scripting language Lingo. You need not have worked with object-oriented Lingo. What you need to know about object-oriented Lingo will be explained. You should be familiar with the capabilities of the ADB I/O, though a summary of what you need to know is provided.


We will start with an introduction to object-oriented Lingo. We will then cover the ADB I/O's hardware and software details relevant to this article. If you are already comfortable with these subjects, you may wish to skip them. Once we have established this ground, we start with building a Director Lingo object which forms a friendly and clean interface to the ADB I/O. At the end of this article you will find a section on troubleshooting and a section with suggestions for enhancements.

Object-Oriented Lingo

Instead of starting with a theoretical discussion of the advantages of object-oriented Lingo, we will dive straight in and put together a simple object-oriented Director movie. I think you will find that the virtues of object-oriented Lingo are much easier to understand once you have seen a concrete example. Our movie consists of three scripts: a parent script, a movie script and a frame script. First we will write the scripts, then we will test and discuss the resulting movie. Don't worry too much if you don't completely 'get it' when we write the scripts. All will be explained.

The parent script

A Director object is described in a parent script. Create a new movie first, then create a parent script by choosing Script from the Window menu (Figure 1). The script window opens. Name the script by typing testParentScript in the field at the top of the scripting window (Figure 2). To make sure that the script is a parent script, click on the info button (Figure 3) and choose parent from the type pop-up menu (Figure 4). Click OK.

Figure 1.Open the script window

Figure 2.Name the script

Figure 3.Press the info button

Figure 4.Make the script a parent script.

Now on to the parent script itself. It consists of three parts: a birthing handler, message handlers and properties. We will discuss each part in turn.

1. The birthing handler

This is where a child object is created. It typically has the form:

on new me
	return me

The birthing handler returns a pointer to a newly created child object. We will add one line of code to our new handler to put a message in the message box telling us that the birthing handler was indeed called.

on new me
	- print some feedback in the message window
	put "the birthing handler of testParentScript was called"

	- return a pointer to the new child object
	return me

2. Message handler

In a message handler you can specify a behaviour of an object. A message handler has the form:

on messageHandler me

We will create an message handler called hello which beeps and puts a message in the message box.

on hello me
	- beep once

	- print some feedback in the message window
	put "Hello World!"

3. Properties

A property is a variable that belongs to an object. It is declared at the top of an object's parent script, preceded by the Lingo keyword property, like this:

property pProperty

Within the parent script you can set or get a property by just its name:

set pProperty = 1
set lVariable = pProperty

When we test the Director movie, you will see how to get at a property from outside of the parent script. Notice how, to maintain an overview, we start a property with the letter p and a local variable with the letter l. For a full description of our naming convention for variables and parameters, please refer to Table 1.

Prefix Suffix Meaning Example
i   input parameter iChannel
l   local variable lResult
o   child object oADB1
p   property variable pUnit
  List list variable pChannel

Table 1.Variable and parameter naming conventions.

For now, we will create a single property called pProperty and two member functions. One to set the value of pProperty and one to get the value of pProperty.

property pProperty

on setProperty me, iValue
	set pProperty = iValue

on getProperty me
	return pProperty

The movie script

To create the movie script, bring up the script window if necessary and click on the + button to create a new script. Name the script movieScript by typing into the field at the top of the script window. Click on the info button. A dialog box appears. Make sure that movie is selected in the type popup-menu. Click OK.

In our movie script we will write two handlers. The first one, startMovie, is called when the movie starts, the second one, stopMovie, is called when the movie stops.

on startMovie

on stopMovie

In this example we will add some code to the startMovie handler only. First we print some feedback to the message window to show that the startMovie handler was called. We then create a global called oTestObject1. Finally, we assign to this global a child object of the testParentScript that we wrote previously by calling the birthing handler.

on startMovie

	- Print some feedback in the message window
	put "startMovie was called."

	- create a global oTestObject1
	global oTestObject1

	- make oTestObject1 a child object of the parentScript testParentScript
	- by calling the birthing handler.
	set oTestObject1 = new (script "testParentScript")


The frame script

We will now create a simple frame script to make our Director movie loop in frame 1. In the score window, double click on the score channel of frame 1 and add one line of code to the exitFrame handler.

on exitFrame
	go to the frame

Testing our movie

It is time to test our movie. If you get stuck at any point and think it is because you did not quite get the explanation above, you may wish to look at testOOMovie.dir. Run the movie and open the message box. Type

put oTestObject1

The message box should display

- <offspring "testParentScript" 2 876a72a>

indicating that oTestObject1 is a child object based on our testParentScript. Don't worry if the alpha-numeric string that you get differs from the one shown above. As this is a memory address it is in fact highly unlikely that it would be identical.

Now try the hello message handler. The syntax for sending a message to a child object is <messageHandler> <childObject>.

hello oTestObject1

The Mac should beep and the message box should display

- "Hello World!"

Now send the getProperty message to our child object. Since getProperty is a function (a handler which returns a value), you need to enclose its parameters in round brackets.

put getProperty (oTestObject1)

The message box shows that pProperty has the value void. No worries, pProperty has not been set yet. Now type:

setProperty oTestObject1, 1

Now try again:

put getProperty (oTestObject1)

And you will see that pProperty has changed its value from void to 1.

setProperty and getProperty are message handlers which we wrote ourselves. Another way to set and get properties of a child object outside of its parent script is:

set the <property> of <childObject> = <value>
set lVariable = the <property> of <childObject>

Notice how we need to add the Lingo 'the' keyword, now that we try to access a property outside of its parent script.

Let's try:

set the pProperty of oTestObject1 = 2
put the pProperty of oTestObject1

The message box shows that pProperty of oTestObject1 now has the value 2.

Play around with accessing properties through the message handlers setProperty and getProperty as well as with accessing the properties directly through the 'the' keyword. You will find that you can mix both methods.

What's the point of object-oriented Lingo?

Before we answer that question, try creating a second child object:

set oTestObject2 = new (script "testParentScript")

The message box responds with

- "new handler of testParentScript was called"

Try entering

put oTestObject2

The message box responds with

- <offspring "testParentScript" 2 876a6ee>

Notice how the address of oTestObject2 differs from the address of oTestObject1. The values of the properties are also individual to each child object. Try

setProperty oTestObject1, 7
setProperty oTestObject2, 666

Now type:

put getProperty (oTestObject1)

The message box shows that the property pProperty is now 7.

And try:

put getProperty (oTestObject2)

This time the message box responds that the property pProperty equals 666. That is pretty kewl: both child objects have a property called pProperty but each can hold a different value.

This example illustrates the three characteristics of object-oriented Lingo that we use in this article. The first is the ability to create multiple child objects based on the same parent script, which are all capable of listening to certain message through the message handlers defined in the parent script. The second characteristic is the ability to hide code - in our case a simple put statement - within the birthing handler of a parent script. This code is automatically executed when a child object is created. The third characteristic is the ability to assign individual property values to child objects. While these characteristics may seem trivial, we can use them to great effect in improving the cleanliness of our code. By hiding set-up details in the birthing handler, these details are prevented from littering your main program. Through the use of properties, variables can be encapsulated within an object. This greatly reduces the need for global variables. Global variables are a major source of bugs in Director because each time you forget to declare a variable as a global, Director will assume you are creating a new, local variable.

You have now seen enough of object-oriented Lingo to be able to understand the ADB I/O parent script that we will build later on. However, there is a lot more to object-oriented Lingo than you have seen so far. To learn more, you can have a look at Director's on-line help under parent script or have a look at Small (1999).

If you are familiar with some other object-oriented language such as C++ or Java, you will probably have worked out the relationship between Lingo and general object-oriented terminology by now. A parent script is a combination of a source and a header file in which you define an object, a birthing handler is a constructor, a message handler is the equivalent of a member function, a property is a data member and a child object is an instance.

The ADB I/O Hardware


You can have a maximum of four ADB I/O units daisy chained to your Mac. Each ADB I/O needs to be set to its own unique ID number.


The ADB I/O has two ports, called port A and port B. Each port has four channels. The channels of port A and B differ in their capabilities.

Port A

Each of the four channels of port A can be configured as either a digital input or a digital output. Analog input is not possible on port A. Each channel can have either a relay or an opto-isolator installed. The ADB I/O comes with relays pre-installed on all channels of port A, making them suitable for digital output. If you wish to use any of these channels for digital input, you will need to replace a channel's relay by an opto-isolator. Through the use of relays or opto-isolators, external electronics are electrically isolated from the ADB I/O.

Port B

port B allows not only digital input and digital output, but also analog input and an analog reference voltage. However, you cannot freely mix all four channel types. Only the following configurations are allowed.

analog in, analog in, analog in, analog in
analog in, analog in, analog in, reference
analog in, analog in, digital in/out, digital in/out
digital in/out, digital in/out, digital in/out, digital in/out

Each channel of port B is equipped with a jumper. A jumper can be set to pull up, pull down or open. For digital input, the jumper needs to be set to either pull-up or pull-down. Note that to switch a channel from digital to analog, you not only need to change the configuration of port B through software, but you also need to move the jumpers.

The ADB I/O Software

To use the ADB I/O with Director 5 or 6, XCMDs are used. These come in a file called ADBIO XCMDs 1.01. You need to make sure that this file resides in the same folder as your Director movie. We can have a look at the functionality offered by the ADBIO XCMDs 1.01 file by creating a simple Director movie (If you get stuck during the creation of this movie you can take a peek at testXCMDs.dir). Start a new movie, create a movie script and add startMovie and stopMovie handlers like this:

on startMovie
	openXLib "ADB I/O XCMDs 1.0.1"

on stopMovie
	closeXLib "ADB I/O XCMDs 1.0.1"

Add a frame script in the score channel of frame 1 to loop the movie. Run the movie and open the message box. Now type


A slew of Xtras, XCMDs and XFCNs show up. We are only interested in the ones that concern the ADBIO:

-	"ADB I/O XCMDs 1.0.1"
-	XCMD: Flash			Id:0		
-	XCMD: Picture			Id:4		
-	XCMD: Palette			Id:5		
-	XCMD: ADBReInit			Id:6701 
-	XCMD: CopyRes			Id:1077 
-	XCMD: ConfigureADBIO		Id:1200 
-	XCMD: SetADBIO			Id:1203 
-	XFCN: GetADBIO			Id:1201 

For now let's look at ConfigureADBIO, SetADBIO and GetADBIO. Here only a short description of these calls is given. For a full description please refer to the ADB I/O manual.


  • ConfigureADBIO has the syntax:
	ConfigureADBIO <Unit> ,<port>,¬
	<Channel1Type>, <Channel2Type>,¬
	<Channel3Type>, <Channel4Type>
  • What does it do? - With ConfigureADBIO you specify what function the four channels of a particular port of a particular ADB I/O unit should fulfill.
  • Parameters - The first parameter, Unit, is used to address a particular ADB I/O unit and can range from 1 to 4. The second parameter, port, is used to address either port A or port B of an ADB I/O. The remaining parameters specify what channel type you wish to assign to each channel of the port. Keep in mind that the capabilities of the ports differ. If you call ConfigureADBIO with unallowed channel types it will return an error message.
  • Return values - ConfigureADBIO returns either 0 (success) or an error string (failure).


SetADBIO has the syntax:
	SetADBIO <Unit>, <Port>, <Channel>¬
	<Number> [,<'high'|'low'>] [,<Time>]
  • What does it do? - You can think of this call as three separate calls. All three versions apply to digital outputs only. The first version sets a channel of a port of a unit to high or low:
	SetADBIO <Unit>, <Port>, <Channel>, <'high'|'low'>

The second sets a channel of a port of a unit to high or low for a certain duration, after which it returns to the opposite state:

	SetADBIO <Unit>, <Port>, <Channel>, <'high"|'low'>, <Time>

The third sets a port of a unit in such a way that the values of its channels form the binary equivalent of a decimal number:

	SetADBIO <Unit>, <Port>, <Number>
  • Parameters - You already know the meaning of the Unit and Port parameters. Channel specifies the number of a channel and should therefore be in the range 1-4. 'high' means relay actived on port A and +5V on port B, 'low' means relay non-active on port A and 0V on port B. Time specifies a duration from 0-25.5 s in 0.1s increments through a integer ranging from 0 to 255. Number ranges from 0 (all channels 'low') to 15 (all channels 'high').
  • Return values - SetADBIO returns either 0 (success) or an error string (failure).


  • GetADBIO has the syntax:
	GetADBIO(<Unit>, <port>[,'Number'])
  • What does it do? - You can think of this call as two separate calls. The first version:
	GetADBIO(<Unit>, <Port>)

returns a channel record with the channel type and channel value for each channel of a port:


The second version:

	GetADBIO(<Unit>, <port>, <"Number">)

returns the decimal equivalent of the binary pattern on the channels of a port.

Return values - GetADBIO returns four lines of channel type and channel value combinations (success) or a decimal number in the range 0-15 (success) or an error string (failure).

Trying it out

Make sure you have at least one ADB I/O unit hooked up to your Mac, run the Director movie you just created and open the message box (Note that according to the ADB I/O manual, your Macintosh should be turned off before you attach or disconnect an ADB I/O). Play about with the three main calls ConfigureADIO, SetADBIO and GetADBIO. For each of these calls, make sure you type the Director keyword put in front of it and that you enclose the call's parameters in brackets so that you get to see the return value in the message box. For example, type:

put configureADBIO (1, "A",¬
"digital out", "digital out",¬
"digital out", "digital out") 

No error should occur and the XCMD should answer with 0.

Now be evil and force some error messages, for example:

put ConfigureADBIO 1, "A",¬
"analog in", "analog in",¬
"analog in", "analog in"

The return value is an error message:

- "Error : Wrong combination of ChannelType parameters"

Clearly, the XCMD is not happy about analog in on port A because only digital in and out are allowed. Let's try something else:

put SetADBIO 1, "A", 5, "high"

Again the return value is an error message:

- "Error : Invalid Channel parameter"

This time the XCMD is not happy because there are only four channels on each port. Play about a bit more. Don't worry about the channel values in the channel record returned by GetADBIO. For the moment no external electronics need to be attached to the ADB I/O, so never mind the values of the channels and ports. Just try to get a general feel for the required parameters and for the return values of the three handlers. Notice how the error messages returned by the three handlers are actually pretty specific. We will make use of this when we build our Director object to control the ADB I/O.

A Director Parent Script for the ADB I/O

Now that we have covered the basics of both object-oriented Lingo and the ADB I/O, we can write a parent script to control the ADB I/O. The parent script is called ADB I/O and forms part of testADBI/OMovie.dir included with the accompanying code. First we discuss the handlers concerning initialization and configuration, then those for input and then those for output. Finally we try out the whole parent script.

Initialization and configuration

Listing 1 shows the parent script's properties, a birthing handler and a configurePort handler.

Listing 1

property pDEBUGMessage, pUnit
property pChannelTypesAList, pChannelTypesBList
property pChannelValuesAList, pChannelValuesBList

This handler creates a child object and hides set-up details.
Example: set oADB1 = new (script "ADB I/O", 1, TRUE)
Possible return values: An object address (success) or 0 (failure).

on new me, iUnit, iDEBUGMessage

	- Requested error reporting.
	set pDEBUGMessage = iDEBUGMessage 

	- We are working with an object per ADB I/O unit.
	set pUnit = iUnit

	- These two lists hold the channel types
	- for port A and port B.
	set pChannelTypesAList = []
	set pChannelTypesBList = []
	- These two lists hold the channel values
	- for port A and port B.
	set pChannelValuesAList = []
	set pChannelValuesBList = []
	- The default initialization of the hardware is:
	- "digital out" for all channels of port A and
	- "digital in" for all channels of port B.
	- Call ConfigureADBIO for both port A and port B
	- with this default initialization.
	- According to the ADB I/O manual this is good practice.
	- The settings can always be changed later.
	- Initialize port A
	- Unit pUnit, port A, all channels digital out
	set lResult = configurePort(me, "A",¬"digital out", "digital out",¬
							"digital out", "digital out")

	if (lResult <> 0) then
		if pDEBUGMessage = TRUE then put lResult
		return 0
	end if

	- Initialize port B
	- Unit pUnit, port B, all channels digital in 
	set lResult = configurePort(me, "B",¬ "digital in", "digital in",¬
  		"digital in", "digital in")

	if (lResult <> 0) then
		if pDEBUGMessage = TRUE then put lResult
		return 0
	end if

return me


This handler configures a port.
configurePort (oADB1, "A", 	"digital out", "digital out",¬
								"digital out", "digital out")
Possible return values:
Empty string "" (success) or an error string (failure).

on configurePort me,	iPort,	iChannel1Type, iChannel2Type,¬
													iChannel3Type, iChannel4Type

	- ConfigureADBIO returns either an empty string
	- "" (success) or an error string (failure).
	put configureADBIO (pUnit, iPort,¬
										iChannel1Type, iChannel2Type,¬
										iChannel3Type, iChannel4Type)¬
										into lResult

	if lResult = 0 then

		case iPort of
			"A":	set pChannelTypesAList =¬
										[	iChannel1Type, iChannel2Type,¬
											iChannel3Type, iChannel4Type]¬
		"B":	set pChannelTypesBList =¬
									[	iChannel1Type, iChannel2Type,¬
											iChannel3Type, iChannel4Type]
		end case

		- An error occurred
		if (pDEBUGMessage=true) then put¬
		"ConfigureADBIO for port" && iPort && "of unit"¬
		&& pUnit && "failed."
	end if

	return lResult

Starting with the birthing handler, we come across the parent script's properties straight away. pUnit holds an integer with the ID number of an ADB I/O device which we pass into the birthing handler through the parameter iUnit.

pDEBUGMessage is a boolean property which we can turn on to get a bit more debugging info in the message window. We can turn it on through the birthing handler's parameter iDEBUGMessage. The chosen approach to debug messages is to report nothing while there are no errors. Only if there is an error will a debug message be printed to the message box. This is because writing to the message box considerably slows down a Director movie. If there are no errors we want to avoid any slow downs as they may cause timing problems.

Next is a set of four lists. pChannelTypesAList is a list which holds the channel types for port A. Similarly, pChannelTypesBList holds the channel types for port B. pChannelValuesAList is a list with the channel values of port A. Finally, pChannelValuesBList is a list with the channel values of port B. All lists are initialized as empty lists.

We use the birthing handler to hide some of the port set-up details of the ADB I/O. The default hardware configuration is all channels of port A configured as digital outputs and all channels of port B configured as digital inputs. We match the default hardware configuration by calling the message handler configurePort for port A and port B. If you look at configurePort you see that it calls ConfigureADBIO and that if it succeeds it sets either the property pChannelTypesAList or pChannelTypesBList to the requested channel types. If you need other settings for either port A or B you can call always call configurePort with the desired channel types after the child object has been created.

Note the flow of error messages: if ConfigureADBIO returns an error message, then configurePort returns an error message and the birthing handler returns 0. Also note the use of the property pDEBUGMessage. If it is true we print some error messages to the message window. ConfigureADBIO is pretty important. During debugging we need to know if it fails and why.

Reading from inputs

We will now look at the calls for getting information from channels and ports. There are four calls: getChannelDigital, getChannelAnalog, getPort and getPortDigital. Let's start with getChannelDigital (Listing 2).

Listing 2


This handler returns the value of a digital input channel.
Example: getChannelDigital (oADB1, "A", 1)
Possible return values: 0 (low), 1 (high) or an error string (failure).

on getChannelDigital me, iPort, iChannel

	- Make sure iChannel is within range 1-4. 
	if iChannel<1 OR iChannel>4 then
		set lResult = "Error : Invalid Channel parameter"
		return lResult
	end if

	- Get the channel types and values for this port.
	put GetADBIO(pUnit, iPort) into lResult

	if word 1 of lResult = "Error" then
		- GetADBIO returned an error.
		return lResult
		- GetADBIO returned no error.
		- Extract the relevant line for the desired channel. 
		put line iChannel of lResult into lThisChannelString

		- Check whether whether our channel is a digital input
		- that is high, low, or not a digital input at all.
		case lThisChannelString of
			"digital in,high":
				- This is a digital input and it is high.				
				return 1
			"digital in,low":				
				- This is a digital input and it is low.				
				return 0
				- This channel does not allow digital input.				
				set lResult =	"Error: Channel" && iChannel &&¬
											"of port" && iPort && "of unit" &&¬
											pUnit && "is not a digital input."

				return lResult
		end case
	end if	

If you just want to look at a single digital input channel, getChannelDigital is the way to do it. First it makes sure iChannel is within the range 1-4, then it calls GetADBIO to retrieve an up-to-date channel record for the port you specify in the iPort parameter. Based on the iChannel parameter it extracts the relevant line from the channel record. It then determines whether the channel is high, low or not a digital channel at all. If the channel is high, getChannelDigital returns 1, if it is low it returns 0. If the channel is not a digital input channel, getChannelDigital returns an error message.

The next call we examine is getChannelAnalog (Listing 3).

Listing 3


This handler returns the value of an analog input channel. Since analog input is only possible on port B, we do not need to pass the port as a parameter.
Example: getChannelAnalog (oADB1, 1)
Possible return values: 0-255 (success) or an error string (failure). 

on getChannelAnalog me, iChannel
	- Make sure iChannel is within range 1-4. 
	if iChannel<1 OR iChannel>4 then
		set lResult = "Error : Invalid Channel parameter"
		return lResult
	end if
	put GetADBIO(pUnit, "B") into lResult
	if word 1 of lResult = "Error" then
		- GetADBIO returned an error.
		return lResult
		- GetADBIO returned no error.
		- Extract the relevant line for the desired channel. 
		put line iChannel of lResult into lThisChannelString
		- Check whether analog in is allowed on this channel.
		if lThisChannelString contains "analog in" then
			- This channel does allow analog input.
			- Extract its value.
			put char 11 to length(lThisChannelString)¬
			of lThisChannelString into lValueString		
			return value(lValueString)

			- This channel does not allow analog input.
			set lResult =	"Error : Channel" && iChannel &&¬
								"of port B of unit" && pUnit &&¬
								"is not a analog input."
			return lResult			
		end if
	end if

getChannelAnalog is the analog equivalent of getChannelDigital: it is the way to go if you need to monitor a single analog input channel. First it makes sure the parameter iChannel is within the range 1-4, then it calls GetADBIO to retrieve an up-to-date channel record for the port you specify in the iPort parameter. Based on the iChannel parameter it extracts the relevant line from the channel record. It then determines whether the channel allows analog input. If the channel is an analog input getChannelAnalog extracts the channel value. If the channel is not a analog input channel getChannelAnalog returns an error message.

But what if you need to monitor multiple channels? Calling getChannelDigital or getChannelAnalog repeatedly is not very efficient, as each time you call them, you in fact call GetADBIO which returns a channel record with the values of all the channels of a port. Reading from or writing to a port too often can make other input devices on the ADB bus act sluggishly (Beehive Technologies recommends not to read more than 12 times per second and not to write more than 6 times per second). A more efficient way to look at multiple channels is by means of getPort (Listing 4).

Listing 4


This handler gets both the channel types and the channel values of the
inputs of a particular port. The channel types are stored in
pChannelTypesAList or pChannelTypesBList, depending on the port. The
channel values are stored in pChannelValuesAList or pChannelValuesBList,
depending on the port.
Example: getPort (oADB1, "A")
Possible return values: 0 (success) or an error string (failure).

on getPort me, iPort

	set lResult = GetADBIO (pUnit, iPort)

	if word 1 of lResult = "Error" then
		- GetADBIO returned an error.
			return lResult
		- GetADBIO returned no error.
		- Work out the channel value and the channel type.
		repeat with lChannel = 1 to 4

			set lThisChannelString = line lChannel of lResult

			case (lThisChannelString) of
				"digital in,low":
					set lChannelType = "digital in"
					set lChannelValue = 0

				"digital in,high":
					set lChannelType = "digital in"
					set lChannelValue = 1

				"digital out,low":
					set lChannelType = "digital out"
					set lChannelValue = 0

				"digital out,high":
					set lChannelType = "digital out"
					set lChannelValue = 1

				"analog ref,255":
					set lChannelType = "analog ref"
					set lChannelValue = 255

					set lChannelType = "analog in"
					put char 11 to length(lThisChannelString) of¬
					lThisChannelString into lChannelValueString
					set lChannelValue = value(lChannelValueString)

			end case

			- Store the channel type and channel value in
			- their respective arrays.
			if (iPort = "A") then			 
				setAt pChannelTypesAList, lChannel, lChannelType
				setAt pChannelValuesAList, lChannel, lChannelValue
				setAt pChannelTypesBList, lChannel, lChannelType
				setAt pChannelValuesBList, lChannel, lChannelValue
			end if
		end repeat
		return 0
	end if	

In getPort we finally make use of the properties pChannelValuesAList and pChannelValuesBList. getPort calls GetADBIO for the desired port. If GetADBIO fails, getPort returns an error message. If getPort executes successfully and returns a channel record, getPort looks at each line of the channel record, puts the channel type in either pChannelTypesAList or pChannelTypesBList, and the channel value in either pChannelValuesAList or pChannelValuesBList. In the process the values of the input channels are converted to integers: 0 or 1 for a digital input and 0-255 for an analog input. After calling getPort you can easily retrieve an input channel's value by using the standard Lingo list access call getAt. You will see how to use the getPort-getAt combo in more detail when we put the parent script into action.

We don't discuss GetPortDigital in detail here. If you look at the code, you will see that it is simply a wrapper for GetADBIO with use of the number parameter. It reads all four channels of a port, converts the binary pattern on the port to a decimal number and returns this number.

Writing to outputs

There are three calls for setting output channels and ports: setChannelDigital, setChannelDigitalDuration and setPortDigital. Let's start with setChannelDigital (Listing 5).

Listing 5


This handler sets a digital output channel high or low.
Example: setChannelDigital (oADB1, "B", 1, 1)
Possible return values:
0 (success) or an error string (failure)

on setChannelDigital me, iPort, iChannel, iChannelValue
	- Check whether the requested channel value is allowed.
	if iChannelValue<>0 AND iChannelValue<>1 then
		- The requested value is not allowed.
		set lResult = "Error: channel value must be 0 or 1"
		return lResult
	end if

	- Try setting the channel to the requested value.
	if iChannelValue=0 then
		set lResult = SetADBIO(pUnit, iPort, iChannel, "low")
		set lResult = SetADBIO(pUnit, iPort, iChannel, "high")
	end if

	return lResult


setChannelDigital is a convenient way of setting a single digital output channel. First it checks whether the requested channel value is 0 or 1. If the channel value is not 0 or 1, setChannelDigital returns an error. Otherwise it uses a call to SetADBIO to try to fulfill the request.

We are not going to cover setChannelDigitalDuration or setPortDigital in detail. If you look at the accompanying code, you will see that setChannelDigitalDuration is similar to setChannelDigital. The main difference is that the former uses the Time parameter in SetADBIO to set a digital output channel for a certain duration. This feature is available on port A only. With SetPortDigital you can set a whole port in one go, providing all the channels are digital outputs.

Taking It All for a Spin

Ok, let's try to put our parent script to the test. If you get stuck in this section, you may wish to peek ahead at the trouble shooting section towards the end of this article.

External electronics

To test the parent script you need to hook up some external electronics to the ADB I/O (Note that according to the ADB I/O manual, your Macintosh should be turned off before you attach or disconnect electrical devices from the ADB I/O). Connect an LED to each channel of port A. Hook up potentiometers to channel 1 and channel 2 of port B. Connect an LED to channel 3 of port B. Finally, connect a push-to-make switch to channel 4 of port B, which connects the channel to ground when you press the switch. If you feel unsure about how to connect these components, have a look at the application notes on the Beehive Technologies website.

Initialization and configuration

Have a look at the startMovie handler in the movie script of testADBIOMovie.dir. There are only three lines of code:

global oADB1 
openXLib "ADB I/O XCMDs 1.0.1"
set oADB1 = new(script "ADB I/O", 1, TRUE)

The three lines should look familiar to you from the testOOMovie.dir and the testXCMDsMovie.dir. The first line creates a global variable called oADB1. The second opens the library with the XCMDs. The third line assigns to the global variable oADB1 a child object based on the ADB I/O parent script by calling the birthing handler. As iUnit parameter we pass 1 (the unit ID number) and as iDEBUGMessage we pass true.

Before we start playing around with the handlers remember that we took the pessimistic approach to debugging messages: we only get to see bad news. No message is good news. Now open the message box, delete any text that litters it, and play the movie. If the message box remains empty that is good news: the birthing handler and the embedded calls to configurePort for port A and port B executed successfully. If you do get an error message you should be able to figure out what is wrong. After all, we carefully passed back the original error message from ConfigureADBIO.

We can still get some kind of confirmation that the birthing handler executed successfully. Let's look at the channel types of port A by typing:

put the pChannelTypesAList of oADB1

The message box responds with:

- [	"digital out", "digital out", "digital out",¬
		"digital out"]

That is good. It is default hardware configuration for port A which we requested in the birthing handler. Now look at the channel types of port B. Type:

put the pChannelTypesBList of oADB1
- [	"digital in", "digital in", "digital in",¬
		"digital in"]

Again, that is what we expected. It is the default hardware configuration for port B which we requested in the birthing handler.

From the electronic components you can infer how we want the ports to be configured. All channels on port A need to be digital outputs. On port B, channels 1 and 2 need to be analog inputs, channel 3 needs to be a digital output and channel 4 a digital input. Since the desired configuration of port A matches the default configuration we do not need to reconfigure port A. The desired configuration of port B differs from the default configuration so we call:

put configurePort (oADB1, "B", 	"analog in", "analog in",¬
															"digital out", "digital in")

The message box should respond with 0 indicating that no errors occurred.

Reading from inputs

We will cover the remaining handlers in the order they occur in the parent script. Let's try reading channel 4 of port B, a digital input.

put getChannelDigital (oADB1, "B", 4)

Providing the jumper on this channel is set to pull-up and you use a push-to-make switch connected to ground, getChannelDigital returns 1 with the switch open and 0 with the switch closed.

Next we try reading an analog input, channel 1 of port B.

put getChannelAnalog (oADB1, 1)

getChannelAnalog should return a value. Now turn the potentiometer connected to this channel and call getChannelAnalog again. Notice how the value of the channel changes. Now try getChannelAnalog with channel 2 of port B. It should behave in the same way.

The last call for reading inputs is GetPort. Let's call it for port B:

put GetPort (oADB1, "B")

Remember that GetPort stores the channel values of a port in either pChannelValuesAList or pChannelValuesBList. Let's have a look at the property pChannelValuesBList.

put the pChannelValuesBList of oADB1

The message box shows a list with four values which correspond with the four channels of port B. We can simply get at each entry in the list through the use of getAt.

put getAt (the pChannelValuesBList of oADB1, 1)

gives us the value of the analog input channel 1 of port B. Likewise,

put getAt (the pChannelValuesBList of oADB1, 2)

gives us the value of the analog input channel 2 of port B. And

put getAt (the pChannelValuesBList of oADB1, 4)

gives us the value of the digital input channel 4 of port B.

List access calls to pChannelValuesAList or pChannelValuesBList must be preceded by a call to getPort. If you do not call getPort first, the lists contain old values which may differ from the current values. Think of getPort and getAt as a combo.

The last of the message handlers for reading from the ADB I/O is getPortDigital. With the components we have got hooked up, we can't really try it out in a meaningful way which would be to convert the binary pattern on a port with four digital input channels to a decimal number. What we can do is show its use with the four digital output channels of port A. First set the port with the XCMD SetADBIO, then read it again with getPortDigital.

SetADBIO 1, "A", 15
put getPortDigital (oADB1, "A")

If you play about with these commands, you will find that you can read back from the port the value you just wrote to it. Note that if you use getPortDigital with a port that has one or more channels set to analog in, you will not get an error message. An analog input channel will have the value 0. While that is not useful in itself, it allows you to work out the values of any remaining digital channels.

Writing to outputs

Luckily, writing to channels and ports is a bit easier than reading from them. Let's write to channel 3 of port B, a digital output. Typing

put setChannelDigital (oADB1, "B", 3, 0)

turns off the LED.

put setChannelDigital (oADB1, "B", 3, 1)

turns on the LED. Try using setChannelDigital on port A.

Next, try setChannelDigitalDuration. Remember, this call only works on port A. Let's try it on channel 1 of port A.

put setChannelDigitalDuration (oADB1, 1, 1, 10)

This turns on the LED for 10 x 0.1 = 1 second after which the LED turns off.

Finally, there is setPort for setting a whole port in one go. Enter

put setPort (oADB1, "A", 15)

This turns on all LEDs on port A, since the binary equivalent of the decimal integer 15 is 1111.

Play around a bit more with all these handlers. Try forcing some errors. For example, try writing to an input channel or reading from an output channel. Error messages will inform you if you ask for the impossible.

Trouble Shooting

Strange bugs but no error messages? Or error messages which completely puzzle you? This section lists some possible causes.


  1. Have you enclosed the parameters in brackets? All our message handlers are in fact functions, that is, they return values. Lingo expects functions to have their parameters enclosed in brackets.
  2. Do the values you pass into the handler match the handler's function prototype? If they do not, confusing error messages may result.
  3. Are all child objects of the ADB I/O parent script declared as globals? If you expect a child object to stay around in memory, while it is actually a local child object which goes out of scope at the end of the handler in which it was created, you can get very confusing errors. If in doubt try using put to print the value of the child object to the message box. If you get void you are half way to finding the bug.


  1. Is your port A channel correctly equipped with a relay or an opto-isolator? If you need digital output on a port A channel you need to have a relay installed, if you need digital input you need an opto-isolator installed. Obviously, if you have a relay installed on a channel and try to configure it as a digital input, or have an opto-isolator installed and try to configure it as a digital output, that channel will not work correctly. But as the processor in the ADB I/O cannot distinguish between relays and opto-isolators ConfigureADBIO will not return an error string.
  2. Is your port B channel jumper correctly set to pull-up, pull-down or open circuit? If you need digital input or digital output on a channel of port B, you need to set the jumper to pull-up or pull-down. If you need analog input you need to disable the jumper (open circuit). If you try analog input with pull-up or pull-down you will get erroneous values, but you will not receive any error message.


To keep the number of files to a minimum I made the testADBI/OMovie.dir self-contained. You may find it convenient to put the parent script in an external cast, so that you can easily include it with different movies.

Another point which you may have noticed is that there is no analog output available on the ADB I/O. However, Application Note 6 on the Beehive Technologies website documents how to use both port A and B to drive four digital potentiometers to create four analog outputs. If you have a need for analog outputs you may wish to extend the ADB I/O parent script with a setChannelAnalog handler which considers each digital potentiometer to be a channel. The prototype would look like:

on setChannelAnalog me, iChannel, iChannelValue


In this article we used object-oriented Lingo to create a friendly and clean interface between the ADB I/O and your Director movie. Object-oriented Lingo allowed us to hide set-up details in the parent script's birthing handler and get rid of global variables through the use of property variables. If you had never done anything like this before, you don't know half how spoiled you are. Director and the ADB I/O form a truly Mac-worthy couple: this is data acquisition 'for the rest of us'. Have fun!


Tom spent a large part of his life trying to work out which letter groupings minimize confusion when spelling out his name on the phone. Now that he has found that Djaj-adi-nin-grat leads to significantly better results than Dja-ja-di-nin-grat, he can fully concentrate on his research on interaction design at Delft University. Since he does not own any cats, you won't find any pictures of them on

Apple Inc.
Microsoft Corpora
Google Inc.

MacTech Search:
Community Search:

Software Updates via MacUpdate

Data Rescue 3.2.4 - Recover lost data on...
Data Rescue is a robust and reliable hard-drive recovery solution for your Mac. Recover lost or deleted files, mount corrupted drives, and more -- Data Rescue offers complete relief from crippling... Read more
Adobe Lightroom 5.6 - Import, develop, a...
Adobe Lightroom software helps you bring out the best in your photographs, whether you're perfecting one image, searching for ten, processing hundreds, or organizing thousands. Create incredible... Read more
OneNote 15.2 - Free digital notebook fro...
OneNote is your very own digital notebook. With OneNote, you can capture that flash of genius, that moment of inspiration, or that list of errands that’s too important to forget. Whether you’re at... Read more
iStat Menus 4.22 - Monitor your system r...
iStat Menus lets you monitor your system right from the menubar. Included are 8 menu extras that let you monitor every aspect of your system. Some features: CPU -- Monitor cpu usage. 7 display... Read more
Ember 1.8 - Versatile digital scrapbook....
Ember (formerly LittleSnapper) is your digital scrapbook of things that inspire you: websites, photos, apps or other things. Just drag in images that you want to keep, organize them into relevant... Read more
OmniPlan 2.3.6 - Robust project manageme...
With OmniPlan, you can create logical, manageable project plans with Gantt charts, schedules, summaries, milestones, and critical paths. Break down the tasks needed to make your project a success,... Read more
Command-C 1.1.1 - Clipboard sharing tool...
Command-C is a revolutionary app which makes easy to share your clipboard between iOS and OS X using your local WiFi network, even if the app is not currently opened. Copy anything (text, pictures,... Read more
Knock 1.1.7 - Unlock your Mac by knockin...
Knock is a faster, safer way to sign in. You keep your iPhone with you all the time. Now you can use it as a password. You never have to open the app -- just knock on your phone twice, even when it's... Read more
Mellel 3.3.6 - Powerful word processor w...
Mellel is the leading word processor for OS X and has been widely considered the industry standard since its inception. Mellel focuses on writers and scholars for technical writing and multilingual... Read more
LibreOffice - Free Open Source o...
LibreOffice is an office suite (word processor, spreadsheet, presentations, drawing tool) compatible with other major office suites. The Document Foundation is coordinating development and... Read more

Latest Forum Discussions

See All

Bio Inc. is $0.99 for the Weekend, Recei...
Bio Inc. is $0.99 for the Weekend, Receives Small Update Posted by Ellis Spice on August 1st, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Happy 7th Birthday Readdle! Thank You fo...
Happy 7th Birthday Readdle! | Read more »
Sharknado: The Video Game Review
Sharknado: The Video Game Review By Lee Hamlet on August 1st, 2014 Our Rating: :: SHARKNA-DON'TUniversal App - Designed for iPhone and iPad Sharknado: The Video Game brings the craziness of the movies to iOS, though it quickly... | Read more »
Clima (Weather)
Clima 1.0 Device: iOS iPhone Category: Weather Price: $.99, Version: 1.0 (iTunes) Description: Clima show you all weather information, just beautifully simple. A series of color bars can tell you at a glance exactly current... | Read more »
Sticky Soccer Review
Sticky Soccer Review By Andrew Fisher on August 1st, 2014 Our Rating: :: STICK THIS GAMEUniversal App - Designed for iPhone and iPad Sticky Soccer puts too much emphasis on the ‘sticky’ and not enough on the ‘Soccer’ or ‘Fun’.   | Read more »
Graphics-less Apocalyptic Adventure A Da...
Graphics-less Apocalyptic Adventure A Dark Room Goes Free for a Limited Time Posted by Rob Rich on August 1st, 2014 [ permalink ] | Read more »
Fraud Tycoon Review
Fraud Tycoon Review By Rob Thomas on August 1st, 2014 Our Rating: :: UNHEALTHY CREDITUniversal App - Designed for iPhone and iPad Fraud Tycoon is a half-baked, messy, promotional tie-in that does their sponsor no favors whatsoever... | Read more »
Guardians of the Galaxy: The Universal W...
Guardians of the Galaxy: The Universal Weapon is on Sale for the Weekend Posted by Rob Rich on August 1st, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Mister Beam Review
Mister Beam Review By Jordan Minor on August 1st, 2014 Our Rating: :: ILLUMINATINGUniversal App - Designed for iPhone and iPad Mister Beam’s puzzles are great. But its platforming? Not so much.   | Read more »
Hook Some More Fun With MapHook’s New Up...
Hook Some More Fun With MapHook’s New Update Posted by Jessica Fisher on August 1st, 2014 [ permalink ] iPhone App - Designed for the iPhone, compatible with the iPad | Read more »

Price Scanner via

13-inch MacBook Airs on sale for $100 off MSR...
B&H Photo has the new 2014 13″ MacBook Airs on sale $100 off MSRP. Shipping is free, and B&H charges NY sales tax only. They also include free copies of Parallels Desktop and LoJack for... Read more
16GB iPad Air on sale for $399, save $100
Best Buy is offering the 16GB WiFi iPad Air for $399.99 on their online store for a limited time. Their price is $100 off MSRP. Choose free shipping or free store pickup (if available). Price is for... Read more
All Over For Tablets Or Just A Maturing, Evol...
CNN’s David Goldman weighs in on tablet sector doom and gloom, asking rhetorically: “Is this the beginning of the end for the tablet?” Answering that, he contends that hysteria and panic are... Read more
Letterspace 1.0.1 – New Free iOS Text Editor...
Bangkok, Thailand based independent developer Sittipon Simasanti has released Letterspace, a new text editor for iPhone, iPad, and iPod touch devices. Letterspace is a note taking app with an... Read more
Save up to $130 on an iPad mini with Apple re...
The Apple Store has Certified Refurbished 2nd generation iPad minis with Retina Displays available for up to $130 off the cost of new models, starting at $339. Apple’s one-year warranty is included... Read more
iPad Cannibalization Threat “Overblown”
Seeking Alpha’s Kevin Greenhalgh observes that while many commentators think Apple’s forthcoming 5.5-inch panel iPhone 6 will cannibalize iPad sales, in his estimation, these concerns are being... Read more
Primate Labs Releases July 2014 MacBook Pro P...
Primate Labs’ John Poole has posted Geekbench 3 results for most of the new MacBook Pro models that Apple released on Tuesday. Poole observes that overall performance improvements for the new MacBook... Read more
Apple Re-Releases Bugfixed MacBook Air EFI Fi...
Apple has posted a bugfixed version EFI Firmware Update 2.9 a for MacBook Air (Mid 2011) models. The update addresses an issue where systems may take longer to wake from sleep than expected, and... Read more
Save $50 on the 2.5GHz Mac mini, plus free sh...
B&H Photo has the 2.5GHz Mac mini on sale for $549.99 including free shipping. That’s $50 off MSRP, and B&H will also include a free copy of Parallels Desktop software. NY sales tax only. Read more
Save up to $140 on an iPad Air with Apple ref...
Apple is offering Certified Refurbished iPad Airs for up to $140 off MSRP. Apple’s one-year warranty is included with each model, and shipping is free. Stock tends to come and go with some of these... Read more

Jobs Board

Position Opening at *Apple* - Apple (United...
**Job Summary** The Apple Store is a retail environment like no other - uniquely focused on delivering amazing customer experiences. As an Expert, you introduce people Read more
Position Opening at *Apple* - Apple (United...
**Job Summary** At the Apple Store, you connect business professionals and entrepreneurs with the tools they need in order to put Apple solutions to work in their Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
Sr. Product Leader, *Apple* Store Apps - Ap...
**Job Summary** Imagine what you could do here. At Apple , great ideas have a way of becoming great products, services, and customer experiences very quickly. Bring Read more
Sr Software Lead Engineer, *Apple* Online S...
Sr Software Lead Engineer, Apple Online Store Publishing Systems Keywords: Company: Apple Job Code: E3PCAK8MgYYkw Location (City or ZIP): Santa Clara Status: Full Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.