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

Introduction

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.

Contents

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
end

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
end

2. Message handler

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

on messageHandler me
end

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

on hello me
	- beep once
	beep

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

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
end

on getProperty me
	return pProperty
end

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
end

on stopMovie
end

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")

end

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
end

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

Units

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.

Ports

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"
end

on stopMovie
	closeXLib "ADB I/O XCMDs 1.0.1"
end

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

showXLib

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

  • 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

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

  • 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:

	<Channel1Type>,<Channel1Value>
	<Channel2Type>,<Channel2Value>
	<Channel3Type>,<Channel3Value>
	<Channel4Type>,<Channel4Value>

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


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

new
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
end

configurePort

This handler configures a port.
Example:
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

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

	return lResult
end

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


getChannelDigital

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
	else
		- 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
				
			otherwise:				
				- 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	
end

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


getChannelAnalog

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
	else	
		- 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)
			
		else

			- 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
end

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


getPort

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
	else
		- 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

				otherwise:
					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
			else
				setAt pChannelTypesBList, lChannel, lChannelType
				setAt pChannelValuesBList, lChannel, lChannelValue
			end if
			
		end repeat
		
		return 0
		
	end if	
end

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


setChannelDigital

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")
	else
		set lResult = SetADBIO(pUnit, iPort, iChannel, "high")
	end if

	return lResult

end

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.

Software

  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.

Hardware

  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.

Enhancements

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

Conclusions

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!

References


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 http://www.io.tudelft.nl/research/vormtheorie/djajadiningrat.html.

 
AAPL
$102.47
Apple Inc.
+2.71
MSFT
$44.88
Microsoft Corpora
+0.80
GOOG
$526.54
Google Inc.
+5.70

MacTech Search:
Community Search:

Software Updates via MacUpdate

RapidWeaver 6.0 - Create template-based...
RapidWeaver is a next-generation Web design application to help you easily create professional-looking Web sites in minutes. No knowledge of complex code is required, RapidWeaver will take care of... Read more
NTFS 12.0.39 - Provides full read and wr...
Paragon NTFS breaks down the barriers between Windows and OS X. Paragon NTFS effectively solves the communication problems between the Mac system and NTFS, providing full read and write access to... Read more
RestoreMeNot 2.0.3 - Disable window rest...
RestoreMeNot provides a simple way to disable the window restoration for individual applications so that you can fine-tune this behavior to suit your needs. Please note that RestoreMeNot is designed... Read more
Macgo Blu-ray Player 2.10.9.1750 - Blu-r...
Macgo Mac Blu-ray Player can bring you the most unforgettable Blu-ray experience on your Mac. Overview Macgo Mac Blu-ray Player can satisfy just about every need you could possibly have in a Blu-ray... Read more
Apple iOS 8.1 - The latest version of Ap...
The latest version of iOS can be downloaded through iTunes. Apple iOS 8 comes with big updates to apps you use every day, like Messages and Photos. A whole new way to share content with your family.... Read more
TechTool Pro 7.0.5 - Hard drive and syst...
TechTool Pro is now 7, and this is the most advanced version of the acclaimed Macintosh troubleshooting utility created in its 20-year history. Micromat has redeveloped TechTool Pro 7 to be fully 64... Read more
PDFKey Pro 4.0.2 - Edit and print passwo...
PDFKey Pro can unlock PDF documents protected for printing and copying when you've forgotten your password. It can now also protect your PDF files with a password to prevent unauthorized access and/... Read more
Yasu 2.9.1 - System maintenance app; per...
Yasu was originally created with System Administrators who service large groups of workstations in mind, Yasu (Yet Another System Utility) was made to do a specific group of maintenance tasks... Read more
Hazel 3.3 - Create rules for organizing...
Hazel is your personal housekeeper, organizing and cleaning folders based on rules you define. Hazel can also manage your trash and uninstall your applications. Organize your files using a... Read more
Autopano Giga 3.7 - Stitch multiple imag...
Autopano Giga allows you to stitch 2, 20, or 2,000 images. Version 3.0 integrates impressive new features that will definitely make you adopt Autopano Pro or Autopano Giga: Choose between 9... Read more

Latest Forum Discussions

See All

Ghost Blade (Games)
Ghost Blade 1.1 Device: iOS Universal Category: Games Price: $4.99, Version: 1.1 (iTunes) Description: Get the most outstanding 3D Action Game from App Store NOW! For those who dare pursue dreams. Masterpiece of Yu Shi Game ---China... | Read more »
Fiete – A Day on a Farm Review
Fiete – A Day on a Farm Review By Amy Solomon on October 21st, 2014 Our Rating: :: A MEMORABLE EXPERIENCEUniversal App - Designed for iPhone and iPad Fiete – A day on a farm in an interactive app for young children full of... | Read more »
Tilt to Live: Gauntlet’s Revenge is Almo...
Tilt to Live: Gauntlet’s Revenge is Almost Here Posted by Jessica Fisher on October 21st, 2014 [ permalink ] One Man Left has announced the official release date of Tilt to Live: Gauntlet’s Re | Read more »
Sago Mini Monsters Celebrates Halloween...
Sago Mini Monsters Celebrates Halloween with Fun Costumes and Special Treats. Posted by Jessica Fisher on October 21st, 2014 [ permal | Read more »
Inferno 2 Review
Inferno 2 Review By Andrew Fisher on October 21st, 2014 Our Rating: :: TWIN STICK GOODNESSUniversal App - Designed for iPhone and iPad With tight controls and awesome, stark visuals, Inferno 2 is loads of fun.   | Read more »
Clips Review
Clips Review By Jennifer Allen on October 21st, 2014 Our Rating: :: CONVENIENT PASTINGUniversal App - Designed for iPhone and iPad Making copying and pasting more powerful than usual, Clips is a great way to move stuff around.   | Read more »
MonSense Review
MonSense Review By Jennifer Allen on October 21st, 2014 Our Rating: :: ORGANIZED FINANCESiPhone App - Designed for the iPhone, compatible with the iPad Organize your finances with the quick and easy to use, MonSense.   | Read more »
This Week at 148Apps: October 13-17, 201...
Expert App Reviewers   So little time and so very many apps. What’s a poor iPhone/iPad lover to do? Fortunately, 148Apps is here to give you the rundown on the latest and greatest releases. And we even have a tremendous back catalog of reviews; just... | Read more »
Angry Birds Transformers Review
Angry Birds Transformers Review By Jennifer Allen on October 20th, 2014 Our Rating: :: TRANSFORMED BIRDSUniversal App - Designed for iPhone and iPad Transformed in a way you wouldn’t expect, Angry Birds Transformers is a quite... | Read more »
GAMEVIL Announces the Upcoming Launch of...
GAMEVIL Announces the Upcoming Launch of Mark of the Dragon Posted by Jessica Fisher on October 20th, 2014 [ permalink ] Mark of the Dragon, by GAMEVIL, put | Read more »

Price Scanner via MacPrices.net

Select MacBook Airs $100 off MSRP, free shipp...
B&H Photo has 2014 a couple of MacBook Airs on sale for $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
13-inch 2.5GHz MacBook Pro on sale for $100 o...
B&H Photo has the 13″ 2.5GHz MacBook Pro on sale for $999.99 including free shipping plus NY sales tax only. Their price is $100 off MSRP. Read more
Strong iPhone, Mac And App Store Sales Drive...
Apple on Monday announced financial results for its fiscal 2014 fourth quarter ended September 27, 2014. The Company posted quarterly revenue of $42.1 billion and quarterly net profit of $8.5 billion... Read more
Apple Posts How-To For OS X Recovery
OS X 10.7 Lion and later include OS X Recovery. This feature includes all of the tools you need to reinstall OS X, repair your disk, and even restore from a Time Machine backup. OS X Recovery... Read more
Mac OS X Versions (Builds) Supported By Vario...
Apple Support has posted a handy resource explaining which Mac OS X versions (builds) originally shipped with or are available for your computer via retail discs, downloads, or Software Update. Apple... Read more
Deals on 2011 13-inch MacBook Airs, from $649
Daily Steals has the Mid-2011 13″ 1.7GHz i5 MacBook Air (4GB/128GB) available for $699 with a 90 day warranty. The Mid-2011 13″ 1.7GHz i5 MacBook Air (4GB/128GB SSD) is available for $649 at Other... Read more
2013 15-inch 2.0GHz Retina MacBook Pro availa...
B&H Photo has leftover previous-generation 15″ 2.0GHz Retina MacBook Pros now available for $1599 including free shipping plus NY sales tax only. Their price is $400 off original MSRP. B&H... Read more
Updated iPad Prices
We’ve updated our iPad Air Price Tracker and our iPad mini Price Tracker with the latest information on prices and availability from Apple and other resellers, including the new iPad Air 2 and the... Read more
Apple Pay Available to Millions of Visa Cardh...
Visa Inc. brings secure, convenient payments to iPad Air 2 and iPad mini 3as well as iPhone 6 and 6 Plus. Starting October 20th, eligible Visa cardholders in the U.S. will be able to use Apple Pay,... Read more
Textkraft Pocket – the missing TextEdit for i...
infovole GmbH has announced the release and immediate availability of Textkraft Pocket 1.0, a professional text editor and note taking app for Apple’s iPhone. In March 2014 rumors were all about... Read more

Jobs Board

Senior Event Manager, *Apple* Retail Market...
…This senior level position is responsible for leading and imagining the Apple Retail Team's global event strategy. Delivering an overarching brand story; in-store, Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
Project Manager / Business Analyst, WW *Appl...
…a senior project manager / business analyst to work within our Worldwide Apple Fulfillment Operations and the Business Process Re-engineering team. This role will work Read more
*Apple* Retail - Multiple Positions (US) - A...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
Position Opening at *Apple* - Apple (United...
…customers purchase our products, you're the one who helps them get more out of their new Apple technology. Your day in the Apple Store is filled with a range of Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.