Acme Server Programming

The normal sequence to use ACME Server is:

Opening/Saving Files

Acme Server is capable of servicing both local and remote clients. The server uses a repository as a temporary holding place for files until they are ready to be brought into the active design by parsing.

There are two methods to get the local file into the repository. Use a traditional FTP program to transfer the file into the repository directory of the server (as set in the server.config file). The other is to transfer the file using the simple ftp messages in AcmeServer to transfer the file(OPEN_FILE_FTP, FILE_DATA_FTP, CLOSE_FILE_FTP).

Once the file has been transferred, the OPEN message will cause the server to look in the repository, and parse the file into an acme design. Once the design has been loaded, the server no longer uses the text file for anything. All operations are perfromed on the acme library design representation.

Saving a file causes the acme representation to be sent back to the client and it is the client's responsibility to save it in some persistent location.

Limitations

This scheme has some limitations which the programmer needs to be concerned with:

  1. If a file of the same name has already been opened and loaded by another application, and you send an OPEN message with a file of the same name, you will be automatically connected to the existing design as an additional user. This can cause erroneous data to appear if for instance one tool has already loaded a "test.acme" and you want to load a different "Test.acme".
  2. If two applications simultaneously try to upload an acme description of the same name, the data may become corrupted.

Ensuring that you use a descriptive name for your acme designs as opposed to generic names like test.acme, system.acme, etc. can prevent these types of errors from occurring.

C/C++ Interface:

The message types are defined in the header file "messages.h".  The message types are just mneomonics for the integer message types needed by the client and server.  The second file contains the data structures needed by the PBIO messages.  This is in the file messagedefs.h. Because this file actually specifies storage, it should be included by only one source file in a program.

To compile this program, be sure your include and library directories point to ../morale/acme/dataexchange and ../morale/acme/pbio and you link in the IO and DE libraries. (libIO.a and libDE.a).

The simple program below shows a C program connecting to an acme server and getting a list of top-level system components.

#include <stdio.h>
#include <DE.h> //this is the dataexchange/pbio standard defs
#include "messages.h" //this is the description of the ACME server messages
#include "messagedefs.h" //this contains the PBIO message descriptions.  IT MUST BE INCLUDED BY ONLY 1 SOURCE FILE IN A PROGRAM!

DExchange de;

int main (int argc, char **argv) {
  basic_msg message; //define a message to send to server
  basic_msg_ptr reply; //a pointer to hold the server response
  DEPort dep;
  char *hostName = "infinia";
  int ACME_Server_Port = 9999;
  char *fileName="Existing.acme";
  char *toolID="MyTool";
  int basic_format; //the DE assigned format ID for a message type
  int reply_format; //hold the value for the reply message format
  FILE *fp;
  char line[512];
/*************************************
** Initialization Stuff
*************************************/
  de = DExchange_create(); //create the data exchange
  //these 2 lines register the formats that will be used for communications
  DExchange_register_format(de, "basic message", basic_message);
  DExchange_register_format(de, "long5 message", long5_message);
  //Now get the DE format number for later use
  basic_format=DEget_format_id(de,"basic message");
/*************************************
** Connect to ACME Server
*************************************/
  //the true here says block till connect complete
  dep=DExchange_initiate_conn(de, hostName, ACME_Server_Port, TRUE);
/**************************************
** Transfer file from client to server
**************************************/
  //Now transfer the file we want to the server
  //first open the file at the server end for saving
  message.msgType=OPEN_FILE_FTP;
  message.param1=fileName;
  message.param2="";
  DEport_write_data(dep, basic_format, &message);
  //check server result to be sure file was opened
  reply=(basic_msg_ptr) DEport_read_data(dep, &basic_format);
  if (reply->msgType != ACME_SUCCESS) {
    //operation failed
    printf("Server open for FTP failed\n");
    exit(0);
  }
  //Now open the file here to read and transfer it to the server
  fp=fopen(fileName,"r");
  message.msgType=FILE_DATA_FTP;
  message.param1=fileName;
  while(fgets(line,512,fp)!=NULL){
    message.param2=line;
    DEport_write_data(dep, basic_format, &message);
  }
  message.msgType=CLOSE_FILE_FTP;
  message.param1=fileName;
  message.param2="";
  DEport_write_data(dep, basic_format, &message);
  reply=(basic_msg_ptr) DEport_read_data(dep, &basic_format);
/*************************************
** Open an ACME Design
*************************************/
  //Now build a message to request a file open for the design
  message.msgType=OPEN;
  message.param1=fileName;
  message.param2=toolID;
  //Now send the message to the server
  DEport_write_data(dep, basic_format, &message);
  //Now get the reply and check for success
  reply=(basic_msg_ptr) DEport_read_data(dep,&reply_format);
  if (reply->msgType!=ACME_SUCCESS) {
    printf("Open file failed at server");
    exit(1);
  } else {
    //first param of reply has sys name
    printf("Listing Components for System %s\n",reply->param1);  
/***************************************
 ** Send request to the server for all components
 ****************************************/
   //Build and send the request for all components
    message.msgType=GET_COMPONENTS;
    message.param1=reply->param1;
    message.param2="";
    DEport_write_data(dep, basic_format, &message);
    //Now loop getting server messages until done
    while (1) {
      reply=(basic_msg_ptr) DEport_read_data(dep,&reply_format);
      if (reply->msgType==COMPONENT_NAME) //is this a component name
        printf("Component = %s\n",reply->param1);
      else
        if (reply->msgType==DATA_DONE) //if no more components then done
          break;
        else {
          printf("OOPS, improper server response");
          break;
        }
    }
  }
  gets(line);
}

Tcl/Tk Interface:

The Tcl/Tk interface requires the tcl package to dynamically load the DEComm package and source the morale.messages.tcl file. The messages file contains an easy way to reference the basic message types, just include the global MessageTypes. A sample Tcl/Tk program that creates a simple ACME system is::

#Sample program to demonstrate DEComm interface
#to acmeServer

#Make a new system and add a simple client-server system

#load the dataexchange shared library
#for UNIX must be DEComm.so for Windows DEComm.dll
if {$tcl_platform(platform)=="windows"} {
load DEComm.dll DEComm
} else {
load DEComm.so DEComm
}
source morale.messages.tcl
tk_messageBox -type ok -message "Init done"

#connect to the ACMEServer on infinia, port 9999
#returns channel number (0-5) if successful, -1 on fail
set pok [InitDE 9999 "infinia"]

if {$pok>=0} {
puts "Connection Successful"
} else {
puts "Connection Failed!"
exit
}

#Make a new top-level system with no parent and family type send out our channel
SendDELongMessage $pok $MessageTypes(NEW) "MyNewSys" "None" "None" "" "" ""
set result [GetDEMessage $pok]

#return type is list element 0
set p0 [lindex $result 0]
if {$p0==$MessageTypes(ACME_FAILURE)} {
puts "Server System Create Failed"
}

#Make the 2 components
SendDELongMessage $pok $MessageTypes(NEW_COMPONENT) "Client" "MyNewSys" "None" "" "" ""
set result [GetDEMessage $pok]

#return type is list element 0
set p0 [lindex $result 0]
if {$p0==$MessageTypes(ACME_FAILURE)} {
puts "Server Component Create Failed"
}

SendDELongMessage $pok $MessageTypes(NEW_COMPONENT) "Server" "MyNewSys" "None" "" "" ""
set result [GetDEMessage $pok]

#return type is list element 0
set p0 [lindex $result 0]
if {$p0==$MessageTypes(ACME_FAILURE)} {
puts "Server Component Create Failed"
}

#Put a port on each
SendDELongMessage $pok $MessageTypes(NEW_PORT) "API_Call" "Server" "MyNewSys" "" "" ""
set result [GetDEMessage $pok]

#return type is list element 0
set p0 [lindex $result 0]
if {$p0==$MessageTypes(ACME_FAILURE)} {
puts "Server Port Create Failed"
}

SendDELongMessage $pok $MessageTypes(NEW_PORT) "API" "Client" "MyNewSys" "" "" ""
set result [GetDEMessage $pok]

#return type is list element 0
set p0 [lindex $result 0]
if {$p0==$MessageTypes(ACME_FAILURE)} {
puts "Server Port Create Failed"
}

#Make a Connector
SendDELongMessage $pok $MessageTypes(NEW_CONNECTOR) "RPC" "MyNewSys" "None" "" "" ""
set result [GetDEMessage $pok]

#return type is list element 0
set p0 [lindex $result 0]
if {$p0==$MessageTypes(ACME_FAILURE)} {
puts "Server Connector Create Failed"
}

#Put the roles on the connector
SendDELongMessage $pok $MessageTypes(NEW_ROLE) "Send" "RPC" "MyNewSys" "" "" ""
set result [GetDEMessage $pok]

#return type is list element 0
set p0 [lindex $result 0]
if {$p0==$MessageTypes(ACME_FAILURE)} {
puts "Server Role Create Failed"
}

SendDELongMessage $pok $MessageTypes(NEW_ROLE) "Receive" "RPC" "MyNewSys" "" "" ""
set result [GetDEMessage $pok]

#return type is list element 0
set p0 [lindex $result 0]
if {$p0==$MessageTypes(ACME_FAILURE)} {
puts "Server Role Create Failed"
}

#Now make an attachment to hook everything up
SendDELongMessage $pok $MessageTypes(NEW_ATTACHMENT) "API_Call" "Server" "Receive" "RPC" "MyNewSys" ""
set result [GetDEMessage $pok]

#return type is list element 0
set p0 [lindex $result 0]
if {$p0==$MessageTypes(ACME_FAILURE)} {
puts "Server Attachment Create Failed"
}

SendDELongMessage $pok $MessageTypes(NEW_ATTACHMENT) "API" "Client" "Send" "RPC" "MyNewSys" ""
set result [GetDEMessage $pok]

#return type is list element 0
set p0 [lindex $result 0]
if {$p0==$MessageTypes(ACME_FAILURE)} {
puts "Server Attachment Create Failed"
}

#System Created now save it.
#When we ask the server to save, it sends us the ASCII description
#so we will have it locally. Loop until we get it all.
SendDEMessage $pok $MessageTypes(SAVE) "ClntSvr.acme" ""
set fileID [open "ClntSvr.acme" w]
while {1} {
#read a block into data
set msg [GetDEMessage 0]
set type [lindex $msg 0]
set data [lindex $msg 1]

if {$type!=$MessageTypes(SAVE)} {
break
} else {
#save the line to a file
puts $fileID $data
}
}

close $fileID

 


Adding New Message Formats

The two message formats included with ACME Server are sufficient to handle the current range of messages, but what if you require a new format for specific types of data?  Fortunately, PBIO was designed to allow complex data types like arrays, records and floats all to be passed in  a platform independent manner.  For really sophisticated uses of PBIO, refer to the documentation for the PBIO package.

Basically adding a new format requires the following steps:
1.  In "messages.h" add the struct definition to the list in the second part of the file.  For instance, let's say we want a message with 2 ints and  a float and we want to call it an Int2F message.  We define a struct like:

  typedef struct {
    int msgType;
    int param1;
    int param2;
    float param3;
  } int2f_msg, *int2f_msg_ptr;

2.  In "messagedefs.h" now add the PBIO definitions.  For a description of the details of this structure, refer to the PBIO documentation.  In general, you can follow the simple structure to build new message definitions.  We define a structure like:

IOField Int2F_message [] = {
   {"msgType","integer",sizeof(int),IOOffset(int2f_msg_ptr,msgType)},
   {"param1","integer",sizeof(int),IOOffset(int2f_msg_ptr,param1)},
   {"param2","integer",sizeof(int),IOOffset(int2f_msg_ptr,param2)},
   {"param3","float",sizeof(float),IOOffset(int2f_msg_ptr,param3)},
   {NULL,NULL,0,0}
};

That's really all there is to creating a new format.  The next section will discuss how to integrate your new message into the server.


Adding New Message Handlers

Let's assume that you have created a new message format to handle some application-specific behavior you need from ACMEServer.  We will use the Int2F format developed in the previous section.
1.  First we need to register the message with the server.  In file "acmeserver.cpp" go to the ACMEServer::registerHandlers() method and add a registration call for the message:

DExchange_register_format(_de, "Int2F message", Int2F_message);

2.  Now you need to tell the server what function will handle the incoming message of this type.  We do this by registering the function in the same ACMEServer::registerHandlers() function:

DExchange_register_function(_de,"Int2F message", handleInt2FMessage, NULL);

3.  Now we have to implement the handleInt2FMessage that we registered.  Message handler functions are implemented in the file "handlers.cpp".  Obviously for a C implementation, you don't need the extern "C" part of the header.  Otherwise, all your handler function signatures should be the same.  For additional information on handlers and other call-back event handlers you can create with dataexchange, refer to the detailed documentation for the dataexchange package.

extern "C" 
int handleInt2FMessage (DExchange de, DEPort dep, int format_id, void *data, int data_length, void *client_data)
{
   int2f_msg_ptr reply = (int2f_msg_ptr) data; //the data input param has the message in it
   printf ("Got a message of type Int2F with type= %i, p1= %i, p2=%i, p3=%d\n", reply->msgType, 
                                                   reply->param1, reply->param2, reply->param3);
}

4.  If you want to call a routine in the server there is a static reference to the server in the handler module, so you can call server functions from your handler by "theServer->myNewHandlerRoutine();"  Of course, you would need to add the new method in the acmeserver.cpp and .h files.

5.  To get info from the ACME design, you go through the server which maintains a reference to the design representation in _theDesignRep  which allows you to call your new function "_theDesignRep->myNewACMERoutine();"


[Next] [Table of Contents] [Previous]