Information
We are currently investigating an issue with the editor of some pages. Please save your work and avoid to create new pages until this banner is gone.
Change Record
...
Change History
...
Table of Contents |
---|
...
AUTHOR
...
SECTIONS/PAGES AFFECTED
...
...
REMARKS
...
...
...
1.0
...
2001-05-21
...
Grega Milcinski, Matej Sekoranja
...
All
...
...
Created
...
...
...
1.1
...
2001-09-13
...
Marija Jakopin
...
All
...
...
Some grammar corrections
...
...
...
1.2
...
2001-12-14
...
Grega Milcinski
...
All
...
...
Replaced device with DO
...
...
...
1.3
...
2001-04-09
...
Bernhard Lopez
...
All
...
...
ACSDO incorporated, some appendixes removed.
...
...
...
...
2001-04-10
...
Bernhard Lopez
...
All
...
...
Changes after comments from Gianluca Chiozzi.
...
...
...
1.4
...
2002-10-15
...
David Fugate
...
All
...
...
New ACSDO macro and new CDB entries. Removed property overriding, as the source is not given in this tutorial. Also adheres to C++ coding standards now.
...
...
...
1.5
...
2002-11-13
...
David Fugate
...
CDB
...
...
Changed CDB COB entry to reflect changes in maci/cdb modules.
...
...
...
1.6
...
2003-06-06
...
David Fugate
...
ALL
...
...
Updated for 2.1 releases. Lots of minor fixes.
...
...
...
1.7
...
2003-11-11
...
Alessandro Caproni
...
All
...
...
Updated for 3.0 releases.
...
...
...
1.8
...
2003-12-01
...
Gianluca Chiozzi
...
Title
...
...
Changed title from BACI specific to generic ACS Components
...
...
...
1.9
...
2004-04-15
...
A. Caproni
...
Header and implementation files
...
...
The example uses smart pointers for properties.
Added a little section to show the old implementation with the macro
...
...
...
1.10
...
2004-04-20
...
A. Caproni
...
...
...
Added notes about the cleanUp method
...
...
...
1.11
...
2005-05-12
...
A. Caproni
...
All
...
...
Updated for ACS4.1
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
The purpose of this document is to illustrate how to create a simple device server based on ACS. A C++ example is developed step-by-step and the implementation is explained (note: the example code is taken from the ACS software module "acsexmpl" with very few modifications).
This document neither explains architecture in depth, nor the low-level communication between the device server and the physical device. However, it is advisable to get in-depth acquaintance with concepts of the Basic Control Interface, as described in detailed technical document (BACI specification *\[R01\]* and other documents, specified in References).
Note: these instructions are written for programming under UNIX. Programming under MS Windows does not differ very much, but there are some minor changes.
ACSCo | ACS Component |
BACI | Basic Control Interface |
CORBA | Common Object Request Broker Architecture |
IDL | Interface Definition Language |
MACI | Management and Control Interface |
CDB | Configuration Database |
[R01] | ACS Basic Control Interface Specification |
Table of Contents |
---|
The purpose of this document is to illustrate how to create a simple device server based on ACS. A C++ example is developed step-by-step and the implementation is explained (note: the example code is taken from the ACS software module "acsexmpl" with very few modifications).
This document neither explains architecture in depth, nor the low-level communication between the device server and the physical device. However, it is advisable to get in-depth acquaintance with concepts of the Basic Control Interface, as described in detailed technical document (BACI specification *\[R01\]* and other documents, specified in References).
Note: these instructions are written for programming under UNIX. Programming under MS Windows does not differ very much, but there are some minor changes.
...
ACSCo
...
ACS Component
...
BACI
...
Basic Control Interface
...
CORBA
...
Common Object Request Broker Architecture
...
IDL
...
Interface Definition Language
...
MACI
...
Management and Control Interface
...
CDB
...
Configuration Database
<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="d715eaab-ef12-40dd-9130-05b0576e4c5e"><ac:plain-text-body><![CDATA[ | [R01] | ACS Basic Control Interface Specification | |||
<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="74d1a0e9-27aa-4481-962d-4b24bbcbe93a"><ac:plain-text-body><![CDATA[ | [R02] | Management and Access Control Interface Specification | |||
<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="1459069b-1d73-439b-9ad7-02fede7e4af0"><ac:plain-text-body><![CDATA[ | [R03] | Definition of BACI types for ACS1.1 ([http://kgb.ijs.si/KGB/Alma/Specs/Definition_of_BACI_Types_for_ACS1.1.pdf | http://kgb.ijs.si/KGB/Alma/Specs/Definition_of_BACI_Types_for_ACS1.0.pdf]) | ]]></ac:plain-text-body></ac:structured-macro> | |
<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="992e90fe-d8e7-497b-80cf-76a1de682742"><ac:plain-text-body><![CDATA[ | [R04] | Logging and Archiving ([http://kgb.ijs.si/KGB/Alma/Specs/Logging_and_Archiving.pdf | http://kgb.ijs.si/KGB/Alma/Specs/Logging_and_Archiving.pdf]) | ]]></ac:plain-text-body></ac:structured-macro> | |
<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="4850cc47-ce95-484b-bb31-20b9a462679d"><ac:plain-text-body><![CDATA[ | [R05] | Advanced CORBA Programming with (Henning, Vinoski) | ]]></ac:plain-text-body></ac:structured-macro> | ||
<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="4723e7ee-7d05-4647-8036-63e53b925737"><ac:plain-text-body><![CDATA[ | [R06] | C++ Programming Language Third Edition (Stroustrup) | <ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="e0c23f3e-fb3b-43c3-81bc-17fdb070f165"><ac:plain-text-body><![CDATA[ | [R07] | (http://kgb.ijs.si/KGB/Alma/Specs/ACS_Basic_Control_Interface_Specification.pdf) |
[R02] | Management and Access Control Interface Specification ACS Software Module "acsexmpl" | ||||
[R03] | Definition of BACI types for ACS1.1 | <ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="8f9d6ab3-8bc3-4a62-a3be-14d5540e2b74"><ac:plain-text-body><![CDATA[ | [R08] | CDB Tutorial ([http://kgb.ijs.si/KGB/Alma/Docs/CDB.pdfSpecs/Definition_of_BACI_Types_for_ACS1.1.pdf | |
[R04] | Logging and Archiving ([http://kgb.ijs.si/KGB/Alma/Docs/CDBSpecs/Logging_and_Archiving.pdf | ||||
[R05] | Advanced CORBA Programming with (Henning, Vinoski) | ||||
[R06]]></ac:plain-text-body></ac:structured-macro> | C++ Programming Language Third Edition (Stroustrup) | ||||
[R07] | ACS Software Module "acsexmpl" | ||||
[R08] | CDB Tutorial ([http://kgb.ijs.si/KGB/Alma/Docs/CDB.pdf |
First, we need to know what kind of a device (actually Component) we are writing a device server for. Is it a giant motor, which rotates telescopes, simple power supply, device which throws baked pieces of bread out of a toaster, or an object, residing only on the network, which does something useful? Our part of writing a device server should actually not distinguish much; only variable and method names should be different.
Maybe also the types of variables (double, pattern, read-only, etc.) and methods (synchronous and asynchronous), but that should be all. We have to now consider properties that our Component will maintain and also all possible actions. For example: a simple power supply.
...
1 | A class constructor is always the method with the name of the class. |
3-5 | In the constructor we create properties. Their names must be composed of the name of the server and property name (for example: TestPowerSupply1:current). In property constructor you must also provide a reference to the component. Here the getComponent() method (inherited from CharacteristicComponentImpl) is used to obtain the Component reference. Every Component has its own instance, which takes care of threads (dispatching). It must have a name and it's Unique Identification Number (UID). UID should always be the same (even when server is restarted) and it will be read from local database. There may not be two servers with the same name and/or same UID! |
10 | The created property is assigned to the smart pointer. This step is necessary if the smart property is created without a pointer to a property in the constructor (see row 5). |
The destructor is empty. The entire cleanup for the properties is managed by the smart pointers.
Code Block | ||
---|---|---|
| ||
PowerSupply::~PowerSupply()
{
} |
Let us come to actions now. All requests for asynchronous actions go through the Activator, which sends this request back to the device server. This is done through the invokeAction() method, where you define what has to be done when a call is received:
Code Block | ||
---|---|---|
| ||
Wiki Markup | ||
\\ \\ \\ The destructor is empty. The entire cleanup for the properties is managed by the smart pointers. \\ *PowerSupply{*}::~{*}PowerSupply{*}() \{ \} \\ Let us come to actions now. All requests for asynchronous actions go through the Activator, which sends this request back to the device server. This is done through the invokeAction() method, where you define what has to be done when a call is received: \\ /* --------------- \[ Action implementator interface \] -------------- */ \\ ActionRequest PowerSupply::invokeAction(int function, BACIComponent* cob, const int& callbackID, const CBDescIn& descIn, BACIValue* value, Completion& completion, CBDescOut& descOut) \{ switch (function) \{ case *ON{*}_ACTION: return *on{*}ActiononAction(cob, callbackID, descIn, value, completion, descOut); case *OFF{*}_ACTION: return *off{*}ActionoffAction(cob, callbackID, descIn, value, completion, descOut); case *RESET{*}_ACTION: return *reset{*}ActionresetAction(cob, callbackID, descIn, value, completion, descOut); default: return reqDestroy; \} \} \\ \\ The implementation of the functions we defined in the previous method: \\ |
...
} |
The implementation of the functions we defined in the previous method:
Code Block | ||
---|---|---|
| ||
/* ------------------ |
...
[ Action implementations |
...
] ----------------- */ |
...
/// implementation of async. on() method |
...
ActionRequest |
...
PowerSupply:: |
...
onAction(BACIComponent* cob, const int& callbackID |
...
, const CBDescIn& descIn, BACIValue* value, Completion& completion, CBDescOut& descOut) { ACS_DEBUG_PARAM("::PowerSupply::onAction", "%s", getComponent()->getName()); |
...
........... |
...
completion = ACSErrTypeOK::ACSErrOKCompletion(); |
...
// complete action requesting done invokation, |
...
// otherwise return reqInvokeWorking and set descOut.estimated_timeout |
...
return reqInvokeDone;
} |
11 | Here comes real implementation of the action – like communication with physical device, setting it on, off, etc. (for example: a devIO->write(...)). If action will not be completed in descIn.timeout or action is slow in progress, you should first return "working" (return reqInvokeWorking) and also set estimated timeout (descOut.estimated_timeout) (more about it in [R02], part 3.3.1.2). | ]]></ac:plain-text-body></ac:structured-macro> |
<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="93ffd88f-9e7c-49ae-a240-720cc6a73dde"><ac:plain-text-body><![CDATA[ | 13 | To inform client about possible errors, set completion (read [R02], part 3.2). In this case everything is ok and we set completion to OkCompletion.]]></ac:plain-text-body></ac:structured-macro> |
18 | When action is completed, return done (return reqInvokeDone). |
...
, return done (return reqInvokeDone). |
We have to describe the device server’s actions (like on(), off(),
...
reset()).
...
These
...
are
...
asynchronous
...
(usually
...
they
...
take
...
some
...
time
...
and
...
we
...
do
...
not
...
want
...
to
...
block
...
a
...
client
...
while
...
executing)
...
so
...
we
...
have
...
to
...
register
...
the
...
action
...
to
...
the
...
Activator
...
that
...
will
...
then
...
do
...
the
...
rest
...
of
...
the
...
procedure.
...
For example,
...
calling
...
on()
...
will
...
just
...
register
...
that
...
action
...
inside
...
a
...
queue
...
until
...
Activator
...
calls
...
PowerSupply::invokeAction
...
which
...
will
...
in
...
turn,
...
call
...
onAction().
The client must provide a pointer to it’s implementation of callbacks and the structure of type CBDescIn (that identifies the callback) as an argument of the registerAction() method. Our device server sends these parameters to Activator and also adds a pointer to itself and a description of an action (integer – ON_ACTION will be replaced with 1, OFF_ACTION with 2 and so on).
Your job is to write one block of a code for each action and change only the last argument of the method registerAction – an integer which is later sent back to the device server, to the method invokeAction(). First argument of registerAction() method – in this case BACIValue::type_null – is a callback type.
Code Block | ||
---|---|---|
| ||
void PowerSupply::on \\ The client must provide a pointer to it's implementation of callbacks and the structure of type CBDescIn (that identifies the callback) as an argument of the registerAction() method. Our device server sends these parameters to Activator and also adds a pointer to itself and a description of an action (integer - ON_ACTION will be replaced with 1, OFF_ACTION with 2 and so on). \\ Your job is to write one block of a code for each action and change only the last argument of the method registerAction - an integer which is later sent back to the device server, to the method invokeAction(). First argument of registerAction() method - in this case BACIValue::type_null - is a callback type. \\ void *PowerSupply::on* (ACS::CBvoid_ptr cb, const ACS::CBDescIn & desc) throw(CORBA::SystemException) \{ getComponent()->registerAction(BACIValue::type_null, cb, desc, this, *ON{*}_ACTION); \} \\ void *PowerSupply::off* (ACS::CBvoid_ptr cb, const ACS::CBDescIn & desc) throw(CORBA::SystemException) \{ getComponent()->registerAction(BACIValue::type_null, cb, desc, this, *OFF{*}_ACTION); \} \\ void *PowerSupply::reset* (ACS::CBvoid_ptr cb, const ACS::CBDescIn & desc) throw(CORBA::SystemException) \{ getComponent()->registerAction(BACIValue::type_null, cb, desc, this, *RESET{*}_ACTION); \} \\ |
throw(...)
...
defines
...
what
...
kind
...
of
...
exceptions
...
are
...
available.
If a client wants to do something with current, readback, and other properties, it must get a reference to these properties. This is done in following code:
Code Block | ||
---|---|---|
| ||
ACS::RWdouble_ptr PowerSupply::current \\ \\ If a client wants to do something with current, readback, and other properties, it must get a reference to these properties. This is done in following code: \\ ACS::{*}RWdouble{*}_ptr *PowerSupply::current{*}() throw(CORBA::SystemException) \{ if (m_current_sp==0) return ACS::{*}RWdouble{*}::_nil(); \\ ACS::{*}RWdouble{*}_var prop = ACS::{*}RWdouble{*}::_narrow(m_current_sp->getCORBAReference()); return prop._retn(); \} \\ ACS::{*}ROdouble{*}_ptr *PowerSupply::readback{*}() throw(CORBA::SystemException) \{ if (m_readback_p==0) return ACS::{*}ROdouble{*}::_nil(); \\ ACS::{*}ROdouble{*}_var prop = ACS::{*}ROdouble{*}::_narrow(m_readback_sp->getCORBAReference()); return prop._retn(); \} \\ ACS::{*}ROpattern{*}_ptr *PowerSupply::status{*}() throw(CORBA::SystemException) \{ if (m_status_p==0) return ACS::{*}ROpattern{*}::_nil(); \\ ACS::{*}ROpattern{*}_var prop = ACS::{*}ROpattern{*}::_narrow(m_status_sp->getCORBAReference()); return prop._retn(); \} \\ |
One
...
must
...
write
...
accessors
...
for
...
every
...
property,
...
changing
...
the
...
name
...
of
...
a
...
property
...
(current),
...
variable
...
(m_current_sp)
...
and
...
type
...
of
...
a
...
property
...
(replace
...
all
...
fields
...
where
...
RWdouble
...
appears
...
with
...
ROdouble
...
or
...
ROpattern).
...
Now
...
we
...
are
...
really
...
near
...
the
...
end.
...
We
...
have
...
to
...
add
...
MACI
...
DLL
...
support
...
functions.
...
For
...
this
...
purpose
...
we
...
use
...
the
...
macro
...
MACI_DLL_SUPPORT_FUNCTIONS(class_name):
Code Block | ||
---|---|---|
| ||
\\ /* --------------- \[ MACI DLL support functions \] -----------------*/ \\ #include <maciACSComponentDefines.h> MACI_DLL_SUPPORT_FUNCTIONS ({*}PowerSupply{*}) \\ /* ----------------------------------------------------------------*/ \\ |
As
...
we
...
said
...
at
...
the
...
beginning
...
of
...
the
...
paragraph,
...
it
...
is
...
possible
...
to
...
use
...
the
...
properties
...
without
...
smart
...
pointers
...
even
...
if
...
we
...
discourage
...
this
...
implementation.
...
We
...
briefly
...
describe
...
herein
...
the
...
changes
...
in
...
the
...
previous
...
code
...
if
...
smart
...
pointers
...
were
...
not
...
used.
...
In
...
the
...
include
...
file
...
each
...
property
...
is
...
defined
...
without
...
the
...
templetized
...
smart
...
pointer
...
type
...
and,
...
of
...
course,
...
the
...
baciSmartPropertyPointer.h
...
is
...
not
...
needed.
...
Note
...
that
...
the
...
names
...
of
...
the
...
variables
...
terminate
...
with
...
_p
...
and
...
not
...
with
...
_sp
...
to
...
highlight
...
that
...
we
...
are
...
now
...
using
...
pointers
...
instead
...
of
...
smart
...
pointers.
...
ROdouble*
...
m_readback_
...
p
ROdouble*
...
m_readback_p;
...
RWdouble*
...
m_current_p;
...
In
...
the
...
constructor
...
these
...
pointers
...
are
...
first
...
initialized
...
to
...
0
...
then
...
each
...
property
...
is
...
created
...
in
...
the
...
same
...
way
...
they
...
are
...
created
...
with
...
smart
...
pointers.
...
The
...
CHARACTERISTIC_COMPONENT_PROPERTY
...
macro must be
...
implemented
...
for
...
each
...
BACI
...
property.
...
The
...
macro
...
performs
...
error
...
checking
...
and
...
generates
...
a
...
description
...
of
...
the
...
property.
...
This
...
macro
...
must
...
not
...
be
...
used
...
with
...
smart
...
pointers
...
because
...
they
...
take
...
care
...
of
...
all
...
the
...
initializations
...
automatically.
...
The
...
following
...
is
...
the
...
constructor
...
without
...
using
...
smart
...
pointers
...
for
...
the
...
properties.
Code Block | ||
---|---|---|
| ||
PowerSupply::PowerSupply \\ *PowerSupply{*}::{*}PowerSupply{*}(const ACE_CString& name, maci::ContainerServices * containerServices): CharacteristicComponentImpl(name,containerServices), * m_readback_p({*}0{*}),* * m_current_p({*}0{*}),* * m_status_p({*}0{*}) \{* m_readback_p = new *ROdouble{*}(name+":{*}readback{*}", getComponent()); CHARACTERISTIC_COMPONENT_PROPERTY(readback, m_readback_p); \\ m_current_p = new *RWdouble{*}(name+":{*}current{*}", getComponent()); CHARACTERISTIC_COMPONENT_PROPERTY (current, m_current_p); \\ m_status_p = new *ROpattern{*}(name+":{*}status{*}", getComponent()); CHARACTERISTIC_COMPONENT_PROPERTY(status, m_status_p); *\}* \\ Another difference is in the destructor because for each property we must call the destroy method but not the delete. \\ *PowerSupply{*}::~{*}PowerSupply{*}() \{ ... } |
Another difference is in the destructor because for each property we must call the destroy method but not the delete.
The rest of the code remains the same apart for the renaming of the variables.
Code Block | ||
---|---|---|
| ||
PowerSupply::~PowerSupply() { ... (m_readback_p) \{ m_readback_p->destroy(); m_readback_p = 0; \} (m_current_p) \{ m_current_p->destroy(); m_current_p = 0; \} if (m_status_p) \{ m_status_p->destroy(); m_status_p = 0; \} \}The rest of the code remains the same apart for the renaming of the variables. \\ \\ \\ \\ |
...
m_status_p = 0; }
} |
As we saw writing the constructor of the component, it receives a pointer to a maci::ContainerServices object. Really, maci::ContainerServices is an interface and the component receives an implementation of that interface defined in the maci module. The developer is free to replace this default implementation with another one that better fits its needs.
Throw the ContainerServices object the Containers offers the component a set of services freeing the developer of the details of their implementation.
The getContainerServices() method returns a pointer to the container services object so there is no need to store the container services object received in the constructor in a local variable.
Here I report a brief description of the services of the default implementation of the ContainerServices in the maci module. For For further details, have a look at the doxygen documentation.
Code Block | ||
---|---|---|
| ||
ACE_CString getName() // Return the name of the component |
...
PortableServer::POA_var getPOA() // Return a reference to the POA |
...
// Get a Component of type T |
...
template<class T> T* getComponent(const char *name) |
...
// Get a dynamic component of typoe T |
...
template<class T> T* getDynamicComponent(maci::ComponentSpec |
...
compSpec, bool markAsDefault); // Get the default component (of type T) that implements the idl interface template<class T> T* getDefaultComponent(const char* idlType); // get the descriptor of a component maci::ComponentInfo getComponentDescriptor(const char* componentName) // Find components using their name or their type ACE_CString_Vector findComponents(const char *nameWilcard, const char *typeWildcard) void releaseComponent(const char *name) // release the component with the given name void releaseAllComponents() // Release all the components CDB::DAL_ptr getCDB() // Return a reference to DAL that allows accessing the CDB |
...
PortableServer::POA_var getOffShootPOA() // Return the offShoot POA |
...
// Activate a CORBA servant that implements the offshoot interface |
...
ACS::OffShoot_ptr activateOffShoot(PortableServer::Servant cbServant) |
...
// Deactivate a CORBA offShoot servant |
...
void deactivateOffShoot(PortableServer::Servant cbServant) |
...
...
...
Code Block | ||
---|---|---|
| ||
ACS_TRACE("::PowerSupply::~PowerSupply"); \\ ACS_DEBUG("::PowerSupply::~PowerSupply", "COB destroyed"); \\ ACS_LOG(LM_RUNTIME_CONTEXT, "PowerSupply/DLLOpen", (LM_ERROR, "Failed to create Component - constructor returned 0!")); |
Find
...
out
...
more
...
about
...
this
...
in
...
...
...
...
...
To compile, please use the acsMakefile and the ALMA directory structure. All files should be in subdirectories of <xxxx>/ws/ or <xxxx>/, where <xxxx> represents the name of module. If you have made it this far, you should have these files (although the names should be different...):
Next, create a Makefile (or preferably just change the existing one). It should be located in: <xxxx>/src/Makefile
Since the acsMakefile is rather large and most targets will not be modified, I am going to write only the parts where something has to be written/changed.
Name of main header file – the one, which is described in this document:
No Format |
---|
# # |
...
Includes (.h) files (public and local) |
...
# --------------------------------- |
...
INCLUDES = acsexmplPowerSupplyImpl.h |
...
INCLUDES_L |
...
|
The name, which is written behind
...
“LIBRARIES =
...
”, will be the name of the created library. For each library
...
“<name of library>_
...
OBJECTS” has to be defined:
No Format |
---|
# # |
...
Libraries (public and local) |
...
# ---------------------------- |
...
LIBRARIES = acsexmplPowerSupplyImpl LIBRARIES_L = |
...
acsexmplPowerSupplyImpl_OBJECTS = acsexmplPowerSupplyImpl |
...
\ acsexmplPowerSupplyCurrentImpl acsexmplPowerSupplyDLL\ acsexmplPowerSupplyImpl_LIBS = acsexmplPowerSupplyStubs |
Name of database configuration file:
No Format |
---|
# # |
...
Configuration Database Files |
...
# ---------------------- |
...
CDB_SCHEMAS = PowerSupply |
Name of IDL file:
No Format |
---|
# # |
...
IDL FILES |
...
# |
...
IDL_FILES = acsexmplPowerSupply |
...
USER_IDL = |
Then you have to run it:
make clean all man install
When the compiler finishes, it should create files lib<name of libraries>.so and .a (in our case libacsexmplPowerSupply.so and libacsexmplPowerSupply.a). When you use the
...
“install” tag, binaries, libraries documentation files, etc., will be copied into
...
the $INTROOTdirectory.
Appendix
A number of insignificant details have been removed from the PowerSupply example in this document. For the most up-to-date and complete documentation, please go to http://www.eso.org/~gchiozzi/AlmaAcs/OnlineDocs/acsexmpl/ws/doc/html/index.html. This webpage also contains documentation on even more examples that can be found in the ALMA CVS repository (ACS/LGPL/CommonSoftware/acsexmpl/*).