Frontend user code: Difference between revisions

From MidasWiki
Jump to navigation Jump to search
No edit summary
 
(30 intermediate revisions by 2 users not shown)
Line 1: Line 1:
Links
{{Pagelinks}}
==== Links ====
 
= Links =
<div style="column-count:3;-moz-column-count:3;-webkit-column-count:3">
<div style="column-count:3;-moz-column-count:3;-webkit-column-count:3">
* [[Frontend Operation]]
* [[Frontend Operation]]
* [[Frontend Application]]  
* [[Frontend Application]]  
* [[Equipment List Parameters]]
* [[Equipment List Parameters]]
* [[Slow Control System]]
* [[Equipment Flags]]
* [[Equipment Flags]]
* [[Event Notification (Hot-Link)]]
</div>
</div>


== Introduction==
= Introduction =
This section describes the features of the user-written part of a [[Frontend Operation#Frontend|Frontend]], referred to here as '''frontend.c'''. To make a custom frontend, users will usually modify one of the templates provided in the MIDAS package for their particular hardware and other requirements. A partial list of the templates provided is shown below:
This section describes the features of the user-written part of a "C-style" [[Frontend Operation#Frontend|Frontend]], referred to here as '''frontend.c'''.
 
You can also write frontends using [[Frontend_user_code_(object_oriented_-_TMFE)|object-oriented C++ (TMFE)]] or [[Python]].
 
The frontend system has evolved over the decades, and contains some legacy features that are not often used (e.g. interrupt handlers). For backwards-compatibility, these features are still present, but this does require a bit more boiler-plate code to be written for each frontend. We will first document every feature supported by the frontend system, then explain a slightly more user-friendly wrapper (mfed.cxx) that helps reduce the amount of boiler-plate needed for a new frontend.
 
= Frontend Templates =
To make a user-written or custom frontend, users will usually modify one of the '''templates''' provided in the MIDAS package
under [[Environment Variables#MIDASSYS|$MIDASSYS]]/examples/ for their particular hardware and other requirements. Make sure to pick the closest template, e.g. if writing a [[Slow Control System|Slow Control]] frontend, pick a slow-control template. For each example frontend, a Makefile is also provided.
 
A partial list of the templates provided is shown below:
{| style="text-align: left; width: 60%; background-color: rgb(255, 255, 255);" border="3" cellpadding="2" cellspacing="2"
{| style="text-align: left; width: 60%; background-color: rgb(255, 255, 255);" border="3" cellpadding="2" cellspacing="2"


Line 22: Line 35:
|VME  
|VME  
|fevmemodules.c
|fevmemodules.c
| ../midas/examples/Triumf/c/
| $MIDASSYS/examples/Triumf/c/
| Access to VME modules
| Access to VME modules
| C
| C
Line 29: Line 42:
|VME  
|VME  
|fevme.cxx
|fevme.cxx
| ../midas/examples/Triumf/c++/
| $MIDASSYS/examples/Triumf/c++/
| Access to VME modules
| Access to VME modules
| C++
| C++
Line 36: Line 49:
|CAMAC  
|CAMAC  
|frontend.c
|frontend.c
| ../midas/examples/experiment/
| $MIDASSYS/examples/experiment/
| Access to CAMAC modules
| Access to CAMAC modules
| C
| C
|-
|
|mtfe.c
| $MIDASSYS/examples/mtfe/
| '''Multithreaded''' using ring buffer
| C
|-
| Wiener CC-USB
|feccusb.cxx
| $MIDASSYS/examples/mtfe/
| CAMAC/USB demo
| C++


|-
|-
|RS485 bus  
|RS485 bus  
|mscb_fe.c
|mscb_fe.c
| ../midas/examples/slowcont/
| $MIDASSYS/examples/slowcont/
| Slow controls
| '''Slow control''' with [https://midas.psi.ch/mscb/ MSCB]
| C
| C


Line 50: Line 77:
|EPICS
|EPICS
|frontend.c
|frontend.c
| ../midas/examples/epics/
| $MIDASSYS/examples/epics/
| Slow controls
| '''Slow controls'''
| C
 
 
|-
|Camac
|ebfe.c
| $MIDASSYS/examples/eventbuilder/
| '''Event Builder''' (with [[mevb]])
| C
| C
|-
|
|frontend.cxx
| $MIDASSYS/examples/experiment/
| '''mfed''' (wrapper to simplify writing a frontend)
| C++


|}
|}
Line 60: Line 103:
provided in the Midas Package.  
provided in the Midas Package.  


==Frontend code==
The following sections refer to the templates for user frontend code found in the MIDAS packages (see [[Introduction]]).


===Include files===
The following example (from template frontend file fevmemodules.c - see [[Introduction]]) shows the standard include files needed for VME access with VMIC. The user may add any other include files as needed. In this case, header files for several device drivers have also been added.
The optional include file "experim.h" is often included. This is a special include file for ease of communication between the C code and the ODB, and is generated by the user (see [[experim.h]]). The example below shows a typical list of include files for a frontend:


  #include <stdio.h>     // C standard headers
=Frontend code=
Note that there are several kinds of frontend used for different purposes, e.g.
* frontend to read VME or CAMAC hardware, using interrupt or polling (e.g. to read experimental data)
* multithreaded frontend where polling can be in a separate thread
* slow control frontend (can be multithreaded) see also [[Slow Control System]]
* etc.
 
Templates for many types of user frontend code are provided in the MIDAS packages (see [[#Frontend Templates]]).
 
The following sections refer to these templates.
Most of the examples are taken from the [https://bitbucket.org/tmidas/midas/src/develop/examples/basic/largefe.cxx largefe.cxx example]. Documentation on the MIDAS library subroutines to access the ODB (some of which are used in the examples below) can be found in the [[ODB_Access_and_Use|ODB access page]].
 
The user frontend code is then compiled and linked with the system part and the MIDAS library
(see [[Frontend Operation#Frontend|Frontend Task]]).  Makefiles are provided with the templates that can be modified as needed.
 
== Access to command line parameters ==
The function '''mfe_get_args''' gives access to the [[Frontend Application|Frontend]] command line parameters.
 
This example shows how to use it in the frontend user code:
 
  int argc;
  char **argv;
  mfe_get_args(&argc, &argv);
  for (int i = 0; i < argc; i++) {
    // Use argv[i]
  }
 
See [[Frontend_Application#Arguments|Frontend Application Arguments]] for the arguments that are handled automatically by the frontend system.
 
==Include files==
The following example (from template frontend file ''$MIDASSYS/examples/basic/largefe.cxx'') shows the standard include files needed for a frontend. The user may add any other include files as needed (e.g. those needed for VME access).
 
Some legacy frontends may include the '''[[ODB#experim.h include file|experim.h]]''' file. This was an old way of accessing the ODB, but was not very user-friendly when the ODB structure needed to change.
 
The example below shows a typical list of include files for a frontend:
  #include <stdio.h>
  #include <stdlib.h>
  #include <stdlib.h>
  #include "midas.h"    // MIDAS include file
  #include "midas.h"
#include "mvmestd.h"  // VME header file
  #include "mfe.h"
#include "vmicvme.h"  // VMIC header file
#include "vmeio.h"    // optional hardware header files
#include "v1190B.h"
#include "v792.h"
#include "vf48.h"
#include "v1729.h"
  #include "experim.h"   // user-created with odbedit make command


===Global declarations===
'''<div id="Frontend Name"></div>'''
The following example (from template frontend file fevmemodules.c - see [[Introduction]]) shows the global declaration. The declarations are system wide.  Some may be changed to suit the user, but none should not be removed.
==Global Declarations==
The following example (from template frontend file fevmemodules.c - see [[#Frontend Templates]]) shows the global declaration. The declarations are system wide.  Some may be changed to suit the user, but none should not be removed.


; frontend_name  : This value can be modified to reflect the purpose of the code  
; frontend_name  : This value can be modified to reflect the purpose of the code  
; frontend_call_loop : If set to TRUE, the function frontend_loop() runs after every equipment loop. If FALSE, frontend_loop() does not run. The user can add suitable code to this routine if desired (e.g. to check for a condition).
; frontend_call_loop : If set to TRUE, the function frontend_loop() gets called very frequently. If FALSE, frontend_loop() does not run. The user can add suitable code to this routine if desired (e.g. to check for a condition).
; display_period : The time interval (defined in milliseconds) between the refresh of a frontend status display. The value of zero disables the display. If the frontend is started in the background with the display enabled, the stdout should be redirected to the null device to prevent the process from hanging.
; display_period : The time interval (defined in milliseconds) between the refresh of a frontend status display. The value of zero disables the display. If the frontend is started in the background with the display enabled, the stdout should be redirected to the null device to prevent the process from hanging.
; max_event_size : specifies the maximum size (in bytes) of the expected event.
; max_event_size : specifies the maximum size (in bytes) of the expected event.
; event_buffer_size : specifies the maximum size (in bytes) of the buffer to be allocated by the system.
; event_buffer_size : specifies the maximum size (in bytes) of the buffer to be allocated by the system.
; equipment_common_overwrite : whether the definitions in the EQUIPMENT struct override those in the /Equipment/largefe/Common section of the ODB. If FALSE, the values in the struct will be used the first time the program runs, then the ODB values will be used afterwards (e.g. allowing you to change the period of a [[#Event_Types_and_Triggers|periodic equipment]] via the ODB).


See below for an example of global declarations from a frontend.
See below for an example of global declarations from a frontend.


/*-- Globals -------------------------------------------------------*/
  /* The frontend name (client name) as seen by other MIDAS clients  */
  /* The frontend name (client name) as seen by other MIDAS clients  */
  char *frontend_name = "fevmemodules";
  const char *frontend_name = "largefe";
  /* The frontend file name, don't change it */
  /* The frontend file name, don't change it */
  char *frontend_file_name = __FILE__;
  const char *frontend_file_name = __FILE__;
                                                                    //
  /* frontend_loop is called periodically if this variable is TRUE    */
  /* frontend_loop is called periodically if this variable is TRUE    */
  BOOL frontend_call_loop = FALSE;
  BOOL frontend_call_loop = TRUE;
                                                                    //
  /* a frontend status page is displayed with this frequency in ms */
  /* a frontend status page is displayed with this frequency in ms */
  INT display_period = 000;
  INT display_period = 0;
                                                                    //
  /* maximum event size produced by this frontend */
  /* maximum event size produced by this frontend */
  INT max_event_size = 200000;
  INT max_event_size = 10000;
                                                                    //
  /* maximum event size for fragmented events (EQ_FRAGMENTED) */
  /* maximum event size for fragmented events (EQ_FRAGMENTED) */
  INT max_event_size_frag = 5 * 1024 * 1024;
  INT max_event_size_frag = 5 * 1024 * 1024;
                                                                    //
  /* buffer size to hold events */
  /* buffer size to hold events */
  INT event_buffer_size = 10 * 100000;
  INT event_buffer_size = 10 * 10000;
 
 
/* whether the values in EQUIPMENT struct override ODB values */
===Global User Declarations===
  BOOL equipment_common_overwrite = FALSE;
 
After the global declarations, the user may add his or her own declarations.
The following example (from template frontend file fevmemodules.c - see [[Introduction]])  defines various VME hardware parameters.
An example of hardware declarations in a frontend are shown below:


/* Hardware */
MVME_INTERFACE *myvme;
                      //
/* VME base addresses */
DWORD VMEIO_BASE = 0x780000;
DWORD VTDC0_BASE = 0xF10000;
.......
/* Globals */
#define  N_ADC 100
#define  N_TDC 100
#define  N_PTS 5000


==System Function Declarations==


These lines declare the pre-defined system functions which should be present.


===System Function Prototypes===
These prototypes declare the pre-defined system functions which should be present.
  INT frontend_init();
  INT frontend_init();
  INT frontend_exit();
  INT frontend_exit();
Line 142: Line 197:
  INT frontend_loop();
  INT frontend_loop();


==Readout Function Declarations==
Following the previous group is a second group of declarations, which define the readout functions. These depend on the defined equipments, and run when the respective equipment is triggered. In this example, one equipment will be defined, so there is one declaration. The user functions will be described in detail in later sections.


===Readout Function Prototypes===
  INT read_large_event(char *pevent, INT off);
Following the previous group is a second group of prototypes, which define the readout functions. These depend on the defined equipments, and run when the respective equipment is triggered. In this example, two equipments will be defined, so there are two prototypes. The user functions will be described in detail in the following sections.
 
  INT read_trigger_event(char *pevent, INT off);
INT read_scaler_event(char *pevent, INT off);


If using an interrupt, callback function prototypes are also included
If using an interrupt, callback function prototypes are also included
Line 154: Line 207:
  void register_cnaf_callback(int debug);
  void register_cnaf_callback(int debug);


==Equipment List==
This list of structs defines the behaviour of your frontend (e.g. [[#Event_Types_and_Triggers|periodic or polled equipment]]). See [[Equipment List Parameters|Equipment List]] for full documentation of the entries.
Note that these are the default values for each equipment (used the very first time a frontend is run). If [[#Global declarations|equipment_common_overwrite]] is FALSE, then some of the values will subsequently be read from the ODB instead. If [[#Global declarations|equipment_common_overwrite]] is TRUE, then the ODB will be updated with the coded values each time the program runs.
EQUIPMENT equipment[] = {
  {"large",                  /* equipment name */
    {3, 0,                    /* event ID, trigger mask */
      "SYSTEM",                /* event buffer */
      EQ_PERIODIC | EQ_FRAGMENTED,    /* equipment type */
      0,                      /* event source */
      "MIDAS",                /* format */
      TRUE,                    /* enabled */
      RO_ALWAYS,              /* read when running and on transitions */
      2000,                    /* read every 2 sec */
      0,                      /* stop run after this event limit */
      0,                      /* number of sub events */
      0,                      /* log history */
      "", "", ""},
      read_large_event,        /* readout routine */
      NULL, NULL,              /* keep null */
      NULL,                    /* init string */
  },
  {""}
};
In the case of a polled equipment, the struct would be of the form:
    EQUIPMENT equipment[] = {
      { "Trigger",            // equipment name
        {
          ...
          <b>EQ_POLLED</b>,          // equipment type
          ...
          500,                // poll for 500ms
          ...
          "", "", "",},
          read_my_event,    // readout routine
      ...




===Bank Definition and Equipment List===
In the case of a periodic equipment, the struct would be of the form:


See [[Equipment List Parameters#Bank Definition|Bank Definitions]] and [[Equipment List Parameters|Equipment List]] for detailed information.
    EQUIPMENT equipment[] = {
      { "Scaler",          // equipment name
        {   
            ...
            EQ_PERIODIC    // equipment type
            ...
            10000,          // period (read every 10s)
            ...
            "", "", "",},
      read_my_event,  // readout routine
      ...


However, this may be easier to follow if first the sequence of operations, and examples of the the different types of events (polled/interrupt or periodic etc.) and the functions they require in the frontend are introduced.


===Sequence of Operations in the frontend===
==Sequence of Operations in the frontend==


The following table shows the sequence of operations of the  [[Frontend Operation#Frontend|Frontend]] System functions.  These subroutines must be present in ''frontend.c'',
The following table shows the sequence of operations of the  [[Frontend Operation#Frontend|Frontend]] System functions.  These functions must be implemented in the user's code (but may be as simple as just returning <code>SUCCESS</code> if the function is not relevant for your use case). These functions are called by ''mfe.cxx'' at the appropriate time. The System Transition functions are associated with a particular [[Run States and Transitions|Run Transition]] as shown below:
but the contents are coded by the user. These functions are called by ''mfe.c'' at the appropriate time. The System Transition functions are associated with a particular [[Run States and Transitions|Run Transition]] as shown below:


{|  style="text-align: left; width: 60%; background-color: rgb(255, 255, 255);" border="0" cellpadding="2" cellspacing="2"
{|  style="text-align: left; width: 60%; background-color: rgb(255, 255, 255);" border="0" cellpadding="2" cellspacing="2"
Line 217: Line 320:
The system transition functions all run '''prior to''' the equipment functions. This gives the system the chance to take basic action on the transition request (e.g. enable/disable interrupt) before the equipment runs. All the transition routines run with a [[Run States and Transitions#Default Transition Sequency Numbers|Transition Sequence number]] of 500 (the default). This allows users to add additional functions in the frontend that will run before or after any of the transitions (such as a prestart() or a poststop() function). See [[Run States and Transitions#Run Transition Priority|Run Transition Priority]] for more information.
The system transition functions all run '''prior to''' the equipment functions. This gives the system the chance to take basic action on the transition request (e.g. enable/disable interrupt) before the equipment runs. All the transition routines run with a [[Run States and Transitions#Default Transition Sequency Numbers|Transition Sequence number]] of 500 (the default). This allows users to add additional functions in the frontend that will run before or after any of the transitions (such as a prestart() or a poststop() function). See [[Run States and Transitions#Run Transition Priority|Run Transition Priority]] for more information.


===Function frontend_init ===
==Function frontend_init ==




No parameters.
No parameters.


This function runs '''once only''' at the application startup. Users may perform hardware checking, loading/setting of global variables, hot-link settings to the ODB etc. in frontend_init(), e.g.
This function runs '''once only''' at the application startup. Users may perform hardware checking, loading/setting of global variables, mapping of required ODB structures (see [[ODB#experim.h include file|experim.h include file]]),
[[Event Notification (Hot-Link)#How to set up a Hot-Link|setting up hot-links]] etc. in frontend_init(), e.g.




  INT frontend_init()
  INT frontend_init()
  {
  {
   set_equipment_status(equipment[idx].name, "Initializing...", "yellow");
   set_equipment_status(equipment[0].name, "Initializing...", "yellow");
   ....
   ....                
  // Open VME interface
   set_equipment_status(equipment[0].name, "OK", "green");
  status = mvme_open(&myvme, 0);
                                  //
  // Set am to A24 non-privileged Data
  mvme_set_am(myvme, MVME_AM_A24_ND);
                                  //
  // Set dmode to D32
  mvme_set_dmode(myvme, MVME_DMODE_D32)
  ....
                                  //
   set_equipment_status(equipment[idx].name, "OK", "green");
   return SUCCESS;
   return SUCCESS;
  }
  }




====Reporting Equipment Status====
===Reporting Equipment Status===
If running with run control utility mhttpd, a frontend can send an update to the main status page, to report on its progress, using the function set_equipment_status (see above example). This is useful when hardware can take a long time to respond.
If running with the webserver [[mhttpd]], a frontend can send an update to the [[Status Page]], to report on its progress, using the function set_equipment_status() (see above example). This is useful when hardware can take a long time to respond.




===Function begin_of_run===
==Function begin_of_run==


Parameters:
Parameters:
Line 259: Line 354:
  INT begin_of_run (INT runnumber, char * error)
  INT begin_of_run (INT runnumber, char * error)
  {
  {
   ......
   // Read/validate some settings from the ODB (and return FE_ERR_ODB if there's a problem).
  // Set am to A24 non-privileged Data
   // Apply them to the hardware (and return FE_ERR_HW if there's a problem).
  mvme_set_am(myvme, MVME_AM_A24_ND);
   // etc...
   // Set dmode to D32
  mvme_set_dmode(myvme, MVME_DMODE_D32);
                                      //
   //-------- ADCs -------------------
  v792_Setup(myvme, VADC0_BASE, 2);
  v792_ThresholdWrite(myvme, VADC0_BASE,
      (WORD *)&(ts.v792.threshold1));
  v792_DataClear(myvme, VADC0_BASE);
  csr = v792_CSR1Read(myvme, VADC0_BASE);
  printf("Data Ready ADC0: 0x%x\n", csr);
  ........
  // Disable interrupt
  mvme_write_value(myvme, VLAM_BASE+4, 0x0);
  // Reset Latch
  mvme_write_value(myvme, VLAM_BASE, 0x1);
  // Clear pending interrupts
  mvme_write_value(myvme, VLAM_BASE+8, 0x0);
  // Enable interrupt
  inRun = 1;
  mvme_write_value(myvme, VLAM_BASE+4, inRun);
   return SUCCESS;
   return SUCCESS;
  }
  }


 
==Functions pause/resume_run==
 
===Functions pause/resume_run===


Parameters:
Parameters:
Line 313: Line 386:
  }
  }


===Function end_run===
==Function end_run==


Parameters:
Parameters:
Line 323: Line 396:
  INT end_of_run(INT run_number, char *error)
  INT end_of_run(INT run_number, char *error)
  {
  {
   // Stop DAQ for seting up the parameters
   // Stop the hardware.
   vf48_AcqStop(myvme, VF48_BASE);
   // etc...
                      //
  done = 0;
  stop_req = 0;
  inRun = 0;
  // Disable interrupt
  mvme_write_value(myvme, VLAM_BASE+4, inRun);
  trig_level = 0;
  // Close run gate
  vmeio_AsyncWrite(myvme, VMEIO_BASE, 0x0);
   return SUCCESS;
   return SUCCESS;
  }
  }


 
==Function frontend_exit==
===Function frontend_exit===


Parameters: none
Parameters: none


The function runs When the frontend program is shut down. Can be used to release any locked resources like memory, communications ports etc. e.g.
The function runs when the frontend program is shut down. Can be used to release any locked resources like memory, communications ports etc. e.g.


   function frontend_exit()
   function frontend_exit()
Line 350: Line 413:
   }
   }


===Function frontend_loop===
==Function frontend_loop==


Parameters: none
Parameters: none


If the flag frontend_call_loop is set TRUE, this routine is called when the frontend is idle or once between every event. In the following example, it is being used to check for a timeout:
If [[#Global declarations|frontend_call_loop]] is set to TRUE, this routine is called when the frontend is idle and at least once between every event. You could use it for example to check if there has been a timeout from hardware.


  ...
  ...
  BOOL frontend_call_loop = TRUE;
  BOOL frontend_call_loop = TRUE;
  ...
  ...
                                 //
                                  
  INT frontend_loop()
  INT frontend_loop()
  {
  {
   char str[128];
   // Implement any code that needs to be run very frequently
                  //
  if (stop_req && done==0) {
      db_set_value(hDB,0,"/logger/channels/0/Settings/Event limit", &evlimit, sizeof(evlimit), 1, TID_DWORD);
      if (cm_transition(TR_STOP, 0, str, sizeof(str), ASYNC, FALSE) != CM_SUCCESS) {
        cm_msg(MERROR, "VF48 Timeout", "cannot stop run: %s", str);
      }
      inRun = 0;
      // Disable interrupt
      mvme_write_value(myvme, VLAM_BASE+4, inRun);
      done = 1;
      cm_msg(MERROR, "VF48 Timeout","VF48 Stop requested");
  }
   return SUCCESS;
   return SUCCESS;
  }
  }


 
==Event Types and Triggers==
 
The frontend supports several different types of [[Frontend Operation#Frontend event triggers|event trigger]]. The event trigger type is specified by the [[Equipment Flags|Equipment Flag]]  in the  [[Equipment List Parameters|Equipment Declaration]]. Common event types are "polled events" where the Equipment Flag is  [[Equipment Flags#EQ_POLLED|EQ_POLLED]], "interrupts events" where the Flag is [[Equipment Flags#EQ_INTERRUPT|EQ_INTERRUPT]], and "periodic events" where the Flag is [[Equipment Flags#EQ_PERIODIC|EQ_PERIODIC]]. The name of the associated readout routine is specified in the [[Equipment List Parameters|Equipment Declaration]] for each event type.
 
===Event Types and Triggers===
The frontend supports several different types of [[Frontend Operation#Frontend event triggers|event trigger]]. The event trigger type is specified by the  
[[Equipment Flags|Equipment Flag]]  in the  [[Equipment List Parameters|Equipment Declaration]]. Common event types are "polled events" where the Equipment Flag is  [[Equipment Flags#EQ_POLLED|EQ_POLLED]], "interrupts events" where the Flag is [[Equipment Flags#EQ_INTERRUPT|EQ_INTERRUPT]], and "periodic events" where the Flag is [[Equipment Flags#EQ_PERIODIC|EQ_PERIODIC]]. The name of the associated readout routine is specified in the [[Equipment List Parameters|Equipment Declaration]] for each event type.


Polled  and interrupt events (see [[#Event Types|Event Types]]) require several extra functions to handle the hardware that periodic events do not require. These are described below.
Polled  and interrupt events (see [[#Event Types|Event Types]]) require several extra functions to handle the hardware that periodic events do not require. These are described below.


Note that each frontend may contain:
* zero or one polled equipments
* zero or one interrupt equipments
* any number of periodic equipments


===Function poll_event===
==Function poll_event==
Parameters:
Parameters:
* INT '''source'''
* INT '''source'''
Line 395: Line 446:


If the  [[Equipment List Parameters#Equipment Type|Equipment Type]] is EQ_POLLED, the poll_event() routine will be called as often as possible over the corresponding poll time (e.g. 500ms) given by each polling equipment.
If the  [[Equipment List Parameters#Equipment Type|Equipment Type]] is EQ_POLLED, the poll_event() routine will be called as often as possible over the corresponding poll time (e.g. 500ms) given by each polling equipment.
In this case, the [[Equipment List Parameters|Equipment declaration]] would have this form:
    EQUIPMENT equipment[] = {
      { "Trigger",            // equipment name
        {
          ...
          <span style="color:red">EQ_POLLED</span>,          // equipment type
          ...
          500,                // poll for 500ms
          ...
          "", "", "",},
          read_trigger_event,    // readout routine
      ...
          
          
The user must provide suitable code in the routine poll_event(), e.g.
The user must provide suitable code in the routine poll_event(), e.g. reading a register from a VME module to see if any data is available


  INT poll_event(INT source, INT count, BOOL test)
  INT poll_event(INT source, INT count, BOOL test)
Line 432: Line 469:




===Function interrupt_configure===
==Function interrupt_configure==
* INT '''cmd'''  
* INT '''cmd'''  
* INT '''source'''
* INT '''source'''
Line 475: Line 512:
An [[#Event Readout routine|event readout routine]] must also be provided by the user in the frontend.
An [[#Event Readout routine|event readout routine]] must also be provided by the user in the frontend.


===Event Readout routine===
==Event Readout routine==
In the case of [[#Polled and Interrupt Events|POLLED or INTERRUPT events]], the event readout routine is called an [[#Polled or Interrupt readout routine|Interrupt readout routine]].


An event readout routine (called when an event occurs) is usually of the form
An event readout routine is required for all equipment, and is responsible for sending the actual data to midas. The framework calls the event readout routine whenever an equipment has been triggered (e.g. periodicially, or because poll_event() returned TRUE for a polled equipment etc). The function is of the form


  INT function_name ( char *pevent ... )
  INT function_name ( char *pevent ... )
Line 491: Line 527:
where the first argument of the readout function (pevent)  provides the pointer to the newly constructed event, and points to the first valid location for storing the data.
where the first argument of the readout function (pevent)  provides the pointer to the newly constructed event, and points to the first valid location for storing the data.
;NOTE  
;NOTE  
* The return value is the event size, and must be the number of bytes collected in this function.
* <b>The return value is the event size</b>, and must be the number of bytes collected in this function. This is different to almost every other function in midas (where the return value is a status code).
* You can return 0 if you've decided you don't actually want to write this event.
* The event serial number will be incremented by one for every call to the readout routine, as long as the returned size is non-zero.
* The event serial number will be incremented by one for every call to the readout routine, as long as the returned size is non-zero.
* If the returned value is set to zero, the event will be dismissed and the serial number to that event will be decremented by one.




====General readout function====
===General readout function===
If the Equipment type is EQ_INTERRUPT or EQ_POLLED (i.e. [[#Polled and Interrupt Events|Polled or Interrupt event]]) see also [[#Polled or Interrupt readout routine|Polled or Interrupt readout routine]].  
 
A readout function is needed to send out data. This is done using one of the supported [[Event Structure|event structures]] usually [[Event Structure#MIDAS Format Event|"MIDAS" format]] data banks.  The bank format ("MIDAS" in the example above) is declared in the [[Equipment List Parameters]]  [[Equipment List Parameters#Format|Format]] field.
 
An example of a scaler readout routine read_scaler_event() where the data is read out into [[MIDAS Event Construction|MIDAS data banks]] is shown below.  


In this case (e.g. a periodic event) the Equipment declaration may have this form:
INT read_large_event(char *pevent, INT off)
{
  DWORD *pddata;


    EQUIPMENT equipment[] = {
  /* init bank structure */
      { "Scaler",          // equipment name
   bk_init32(pevent);
        {    
 
            ...
  /* create bank (bank names must be 4 chars long) */
            EQ_PERIODIC    // equipment type
  bk_create(pevent, "BIGG", TID_DWORD, (void **) &pddata);
            0,              // interrupt source (ignored)  
            "MIDAS",        // event format
            ...
            10000,          // period (read every 10s)
            ...
            "", "", "",},
      read_scaler_event,  // readout routine
      ...


A readout event will often need to send out data. This is done using one of the supported [[Event Structure|event structures]] usually [[Event Structure#MIDAS Format Event|"MIDAS" format]] data banks.  The bank format ("MIDAS" in the example above) is declared in the [[Equipment List Parameters]]  [[Equipment List Parameters#Format|Format]] field.
  /* fill data (just dummy values in this case) */
  memset((char *) pddata, 0x0000, 100);
  pddata += 1000000;
  memset((char *) pddata - 100, 0xFFFF, 100);


An example of a scaler readout routine read_scaler_event() where the data is read out into [[MIDAS Event Construction|MIDAS data banks]] is shown below.
  /* close the bank */
  bk_close(pevent, pddata);


INT read_scaler_event(char *pevent, INT off)
   /* return the number of bytes we wrote */
{
  return bk_size(pevent);
    DWORD *pdata, a;
                              //
    /* init bank structure; */
    bk_init(pevent);
                              //
    /* create SCLR bank */
    bk_create(pevent, "SCLR", TID_DWORD, &pdata);
                              //
    /* read scaler bank (CAMAC) */
    for (a = 0; a < N_SCLR; a++)
      cam24i(CRATE, SLOT_SCLR, a, 0, pdata++);
                              //    
    /* close SCLR bank */
    bk_close(pevent, pdata);
                              //
    /* return event size in bytes */
    return bk_size(pevent);
  }
  }


Some other examples of event readout routines in this document are
Some other examples of event readout routines in this document are
* [[Event Structure#FIXED Event Construction|FIXED Format event construction]]
* [[Event Structure#FIXED Event Construction|FIXED Format event construction]]
* [[#Polled or Interrupt readout routine||Polled or Interrupt readout routines]]
* [[Super Event]] construction
* [[Super Event]] construction
* [[Slow Control System|Slow Control]]


Many other examples of readout routines can be found in the [[#Introduction|frontend templates]] in the MIDAS package.
Many other examples of readout routines can be found in the [[#Frontend Templates|frontend templates]] in the MIDAS package.


====Polled or Interrupt readout routine====
===Polled or Interrupt readout routine===
In the case of a [[#Polled and Interrupt Events|Polled or Interrupt event]], the content of the memory location pointed to by '''pevent''' (see [[#Event Readout routine|Event Readout routine]]) prior to its use in the readout function, contains the interrupt source bitwise register. This feature can be exploited in order to identify which hardware module has triggered the readout when multiple interrupts have been assigned to the same readout function.
In the case of a [[#Polled and Interrupt Events|Polled or Interrupt event]], the content of the memory location pointed to by '''pevent''' (see [[#Event Readout routine|Event Readout routine]]) prior to its use in the readout function, contains the interrupt source bitwise register. This feature can be exploited in order to identify which hardware module has triggered the readout when multiple interrupts have been assigned to the same readout function.


Line 572: Line 592:
  }
  }
    
    
Note that the LAM_SOURCE macro simply codes the parameters into a bitwise register.
Note that the [[Macros#CAMAC Macros|LAM_SOURCE macro]] simply codes the parameters into a bitwise register.


The readout routine would contains code such as
The readout routine would contains code such as
Line 597: Line 617:


The data will be packed into banks as described for the [[General readout function|general readout function]] above.
The data will be packed into banks as described for the [[General readout function|general readout function]] above.
The examples fevmemodules.c (VME) and frontend.c (CAMAC) (see [[#Introduction|Introduction]] contain a complete example of read_trigger_event().
The example <code>$MIDASSYS/examples/Triumf/c/fevmemodules.c</code> contains a complete example of read_trigger_event().
 




===Manual Trigger===
==Manual Trigger==
Another type of [[Frontend Operation#Frontend event trigger|frontend event trigger]] supported is the "manual trigger",  where the Equipment Flag is [[Equipment Flags#EQ_MANUAL_TRIGGER|EQ_MANUAL_TRIGGER]]. This flag causes an extra button to be present on the [[mhttpd]] web interface to enable the user to trigger the event.
Another type of [[Frontend Operation#Frontend event trigger|frontend event trigger]] supported is the "manual trigger",  where the Equipment Flag is [[Equipment Flags#EQ_MANUAL_TRIGGER|EQ_MANUAL_TRIGGER]]. This flag means that an event can be triggered through the URL <code>?cmd=Trigger/<equipment_name></code> (e.g. <code>http://my.host:8080/?cmd=Trigger/MyEquipmentName</code>). If you add this URL to the [[/Alias ODB tree|/Alias]] part of the ODB, then a link will appear on midas webpages that can be clicked to trigger an event.


In some cases, the same readout code may be used for two types of event: a manual trigger and (say) a poll event. It is possible to determine whether the readout of an event was triggered by a manual trigger or a regular trigger by adding a call to the  [[MIDAS Event Header Macros|event header Macro]] DATA_SIZE in the readout routine:
In some cases, the same readout code may be used for two types of event: a manual trigger and (say) a poll event. It is possible to determine whether the readout of an event was triggered by a manual trigger or a regular trigger by adding a call to the  [[MIDAS Event Header Macros|event header Macro]] DATA_SIZE in the readout routine:
Line 668: Line 687:
  }
  }


 
==Deferred Transition==
===Deferred Transistion===
This option permits the user to postpone any transition issued by any requester until some condition is satisfied.
This option permits the user to postpone any transition issued by any requester
until some condition is satisfied.
For example:
For example:
* It may not be advisable to pause or stop a run until some hardware has turned off a particular valve.  
* It may not be advisable to pause or stop a run until some hardware has turned off a particular valve.  
Line 683: Line 700:
# Provide a callback function to serve the deferred transition  
# Provide a callback function to serve the deferred transition  
# Implement the condition code  
# Implement the condition code  
<b>Note that you should only have ONE frontend that defines a deferred transition</b> (as this niche feature that was implemented with this assumption in mind...).


The following example demonstrates this process:
The following example demonstrates this process:
Line 744: Line 763:


or  <span style="color: purple; font-style:italic;">/runinfo/Requested transition</span> can be set to 0. The transition will then take place on the next stop or start command.
or  <span style="color: purple; font-style:italic;">/runinfo/Requested transition</span> can be set to 0. The transition will then take place on the next stop or start command.
= Compilation using CMake =
Here is a "minimal" CMakeLists.txt file that can be used to compile a user-written frontend (called "myfe" in this case) that uses the mfe framework.
  cmake_minimum_required(VERSION 3.0)
  project(myfe)
 
  # Check for MIDASSYS environment variable
  if (NOT DEFINED ENV{MIDASSYS})
    message(SEND_ERROR "MIDASSYS environment variable not defined.")
  endif()
 
  set(CMAKE_CXX_STANDARD 11)
  set(MIDASSYS $ENV{MIDASSYS})
 
  if (${CMAKE_SYSTEM_NAME} MATCHES Linux)
    set(LIBS -lpthread -lutil -lrt)
  endif()
 
  # Define the executable to be built, and the source code files
  add_executable(myfe.exe myfe.cxx)
 
  # Directories to search for
  target_include_directories(myfe.exe PRIVATE ${MIDASSYS}/include)
 
  # Libraries to link to
  target_link_libraries(myfe.exe ${MIDASSYS}/lib/libmfe.a ${MIDASSYS}/lib/libmidas.a ${LIBS})
One could then build the executable using the folllowing commands:
  mkdir build
  cd build
  cmake ..
  make
Some older operating systems may require the "cmake3" command to be used instead of "cmake".
= Using mfed to reduce the amount of boiler-plate code =
You can include <code>mfed.h</code> and compile against <code>mfed.cxx</code> to reduce the amount of boiler-plate code that needs to be written.
In particular:
* You only need to define the frontend_name and frontend_file_name [[#Global Declarations|globals]] (not display_period etc)
* You only need to declare your event readout functions (not the [[#System Function Declarations|system functions]])
* You still need to define your EQUIPMENT structs
* You need to define frontend_init(), from which you can call install_poll_event(), install_begin_of_run() etc to use your transition functions. If you don't need a pause_run() function, you don't need to declare/define it!
* It does not support interrupt routines
An example can be found in <code>$MIDASSYS/examples/experiment/frontend.cxx</code>.
The full list of functions you can call in your frontend_init() are:
; install_poll_event : Install a function which gets called to check if a new event is available for equipment of type EQ_POLLED.
; install_frontend_exit : Install a function which gets called when the frontend program finishes.
; install_begin_of_run : Install a function which gets called when a new run gets started.
; install_end_of_run : Install a function which gets called when a new run gets stopped.
; install_pause_run : Install a function which gets called when a new run gets paused.
; install_resume_run : Install a function which gets called when a new run gets resumed.
; install_frontend_loop : Install a function which gets called inside the main event loop as often as possible. This function gets all available CPU cycles, so in order not to take 100% CPU, this function can use the ss_sleep(10) function to give up some CPU cycles.
When compiling with cmake, your incantation for the executable would change:
# Without mfed (regular frontend):
add_executable(frontend frontend.cxx)
# With mfed:
add_executable(frontend frontend.cxx ${MIDASSYS}/src/mfed.cxx)
== Minimal example without mfed ==
  #include <stdio.h>
  #include <stdlib.h>
  #include "midas.h"
  #include "mfe.h"
 
  /*-- Globals -------------------------------------------------------*/
  /* The frontend name (client name) as seen by other MIDAS clients  */
  const char *frontend_name = "largefe";
 
  /* The frontend file name, don't change it */
  const char *frontend_file_name = __FILE__;
 
  /* frontend_loop is called periodically if this variable is TRUE    */
  BOOL frontend_call_loop = TRUE;
 
  /* a frontend status page is displayed with this frequency in ms */
  INT display_period = 0000;
 
  /* maximum event size produced by this frontend */
  INT max_event_size = 10000;
 
  /* maximum event size for fragmented events (EQ_FRAGMENTED) */
  INT max_event_size_frag = 5 * 1024 * 1024;
 
  /* buffer size to hold events */
  INT event_buffer_size = 10 * 10000;
 
  /*-- Function declarations -----------------------------------------*/
 
  INT frontend_init();
  INT frontend_exit();
  INT begin_of_run(INT run_number, char *error);
  INT end_of_run(INT run_number, char *error);
  INT pause_run(INT run_number, char *error);
  INT resume_run(INT run_number, char *error);
  INT frontend_loop();
  INT read_large_event(char *pevent, INT off);
 
  /*-- Equipment list ------------------------------------------------*/
 
  #undef USE_INT
  BOOL equipment_common_overwrite = FALSE;
 
  EQUIPMENT equipment[] = {
 
      {"large",                /* equipment name */
        {3, 0,                    /* event ID, trigger mask */
        "SYSTEM",                /* event buffer */
        EQ_PERIODIC | EQ_FRAGMENTED,    /* equipment type */
        0,                      /* event source */
        "MIDAS",                /* format */
        TRUE,                    /* enabled */
        RO_ALWAYS,              /* read when running and on transitions */
        2000,                    /* read every 2 sec */
        0,                      /* stop run after this event limit */
        0,                      /* number of sub events */
        0,                      /* log history */
        "", "", ""},
        read_large_event,        /* readout routine */
        NULL, NULL,              /* keep null */
        NULL,                    /* init string */
        },
 
      {""}
  };
 
  INT frontend_init()
  {
      return SUCCESS;
  }
 
  INT frontend_exit()
  {
      return SUCCESS;
  }
 
  INT begin_of_run(INT run_number, char *error)
  {
      return SUCCESS;
  }
 
  INT end_of_run(INT run_number, char *error)
  {
      return SUCCESS;
  }
 
  INT pause_run(INT run_number, char *error)
  {
      return SUCCESS;
  }
 
  INT resume_run(INT run_number, char *error)
  {
      return SUCCESS;
  }
 
  INT frontend_loop()
  {
      // Do something very frequently
      return SUCCESS;
  }
 
  INT poll_event(INT source, INT count, BOOL test)
      return 0;
  }
 
  INT interrupt_configure(INT cmd, INT source, PTYPE adr)
  {
      switch (cmd) {
      case CMD_INTERRUPT_ENABLE:
        break;
      case CMD_INTERRUPT_DISABLE:
        break;
      case CMD_INTERRUPT_ATTACH:
        break;
      case CMD_INTERRUPT_DETACH:
        break;
      }
      return SUCCESS;
  }
 
 
  INT read_large_event(char *pevent, INT off)
  {
      DWORD *pddata;
 
      /* init bank structure */
      bk_init32(pevent);
 
      bk_create(pevent, "BIGG", TID_DWORD, (void **) &pddata);
      memset((char *) pddata, 0x0000, 100);
      pddata += 1000000;
      memset((char *) pddata - 100, 0xFFFF, 100);
      bk_close(pevent, pddata);
 
      return bk_size(pevent);
  }
== Minimal example with mfed ==
Note that you only need boiler-plate for the features you're actually going to use.
  #include <stdio.h>
  #include <stdlib.h>
  #include "midas.h"
  #include "mfe.h"
 
  /*-- Globals -------------------------------------------------------*/
  /* The frontend name (client name) as seen by other MIDAS clients  */
  const char *frontend_name = "largefe";
 
  /* The frontend file name, don't change it */
  const char *frontend_file_name = __FILE__;
 
 
  /*-- Function declarations -----------------------------------------*/
  INT frontend_init();
  INT my_frontend_loop();
  INT read_large_event(char *pevent, INT off);
 
  /*-- Equipment list ------------------------------------------------*/
 
  #undef USE_INT
  BOOL equipment_common_overwrite = FALSE;
 
  EQUIPMENT equipment[] = {
 
      {"large",                /* equipment name */
        {3, 0,                    /* event ID, trigger mask */
        "SYSTEM",                /* event buffer */
        EQ_PERIODIC | EQ_FRAGMENTED,    /* equipment type */
        0,                      /* event source */
        "MIDAS",                /* format */
        TRUE,                    /* enabled */
        RO_ALWAYS,              /* read when running and on transitions */
        2000,                    /* read every 2 sec */
        0,                      /* stop run after this event limit */
        0,                      /* number of sub events */
        0,                      /* log history */
        "", "", ""},
        read_large_event,        /* readout routine */
        NULL, NULL,              /* keep null */
        NULL,                    /* init string */
        },
 
      {""}
  };
 
  INT frontend_init()
  {
      install_frontend_loop(my_frontend_loop);
 
      // Can also do install_begin_of_run() etc...
 
      return SUCCESS;
  }
 
  INT my_frontend_loop()
  {
      // Do something frequently
      return SUCCESS;
  }
 
  INT read_large_event(char *pevent, INT off)
  {
      DWORD *pddata;
 
      /* init bank structure */
      bk_init32(pevent);
 
      bk_create(pevent, "BIGG", TID_DWORD, (void **) &pddata);
      memset((char *) pddata, 0x0000, 100);
      pddata += 1000000;
      memset((char *) pddata - 100, 0xFFFF, 100);
      bk_close(pevent, pddata);
 
      return bk_size(pevent);
  }
[[Category:Frontend]]

Latest revision as of 14:45, 8 January 2024


Links

Introduction

This section describes the features of the user-written part of a "C-style" Frontend, referred to here as frontend.c.

You can also write frontends using object-oriented C++ (TMFE) or Python.

The frontend system has evolved over the decades, and contains some legacy features that are not often used (e.g. interrupt handlers). For backwards-compatibility, these features are still present, but this does require a bit more boiler-plate code to be written for each frontend. We will first document every feature supported by the frontend system, then explain a slightly more user-friendly wrapper (mfed.cxx) that helps reduce the amount of boiler-plate needed for a new frontend.

Frontend Templates

To make a user-written or custom frontend, users will usually modify one of the templates provided in the MIDAS package under $MIDASSYS/examples/ for their particular hardware and other requirements. Make sure to pick the closest template, e.g. if writing a Slow Control frontend, pick a slow-control template. For each example frontend, a Makefile is also provided.

A partial list of the templates provided is shown below:

Hardware Filename Directory (MIDAS package) Purpose Language
VME fevmemodules.c $MIDASSYS/examples/Triumf/c/ Access to VME modules C
VME fevme.cxx $MIDASSYS/examples/Triumf/c++/ Access to VME modules C++
CAMAC frontend.c $MIDASSYS/examples/experiment/ Access to CAMAC modules C
mtfe.c $MIDASSYS/examples/mtfe/ Multithreaded using ring buffer C
Wiener CC-USB feccusb.cxx $MIDASSYS/examples/mtfe/ CAMAC/USB demo C++
RS485 bus mscb_fe.c $MIDASSYS/examples/slowcont/ Slow control with MSCB C
EPICS frontend.c $MIDASSYS/examples/epics/ Slow controls C


Camac ebfe.c $MIDASSYS/examples/eventbuilder/ Event Builder (with mevb) C


frontend.cxx $MIDASSYS/examples/experiment/ mfed (wrapper to simplify writing a frontend) C++


The features of a typical frontend program are best explained by reference to examples of the user code provided in the Midas Package.


Frontend code

Note that there are several kinds of frontend used for different purposes, e.g.

  • frontend to read VME or CAMAC hardware, using interrupt or polling (e.g. to read experimental data)
  • multithreaded frontend where polling can be in a separate thread
  • slow control frontend (can be multithreaded) see also Slow Control System
  • etc.

Templates for many types of user frontend code are provided in the MIDAS packages (see #Frontend Templates).

The following sections refer to these templates. Most of the examples are taken from the largefe.cxx example. Documentation on the MIDAS library subroutines to access the ODB (some of which are used in the examples below) can be found in the ODB access page.

The user frontend code is then compiled and linked with the system part and the MIDAS library (see Frontend Task). Makefiles are provided with the templates that can be modified as needed.

Access to command line parameters

The function mfe_get_args gives access to the Frontend command line parameters.

This example shows how to use it in the frontend user code:

 int argc;
 char **argv;
 mfe_get_args(&argc, &argv);
 for (int i = 0; i < argc; i++) {
    // Use argv[i] 
 }

See Frontend Application Arguments for the arguments that are handled automatically by the frontend system.

Include files

The following example (from template frontend file $MIDASSYS/examples/basic/largefe.cxx) shows the standard include files needed for a frontend. The user may add any other include files as needed (e.g. those needed for VME access).

Some legacy frontends may include the experim.h file. This was an old way of accessing the ODB, but was not very user-friendly when the ODB structure needed to change.

The example below shows a typical list of include files for a frontend:

#include <stdio.h>
#include <stdlib.h>
#include "midas.h"
#include "mfe.h"

Global Declarations

The following example (from template frontend file fevmemodules.c - see #Frontend Templates) shows the global declaration. The declarations are system wide. Some may be changed to suit the user, but none should not be removed.

frontend_name
This value can be modified to reflect the purpose of the code
frontend_call_loop
If set to TRUE, the function frontend_loop() gets called very frequently. If FALSE, frontend_loop() does not run. The user can add suitable code to this routine if desired (e.g. to check for a condition).
display_period
The time interval (defined in milliseconds) between the refresh of a frontend status display. The value of zero disables the display. If the frontend is started in the background with the display enabled, the stdout should be redirected to the null device to prevent the process from hanging.
max_event_size
specifies the maximum size (in bytes) of the expected event.
event_buffer_size
specifies the maximum size (in bytes) of the buffer to be allocated by the system.
equipment_common_overwrite
whether the definitions in the EQUIPMENT struct override those in the /Equipment/largefe/Common section of the ODB. If FALSE, the values in the struct will be used the first time the program runs, then the ODB values will be used afterwards (e.g. allowing you to change the period of a periodic equipment via the ODB).

See below for an example of global declarations from a frontend.

/*-- Globals -------------------------------------------------------*/
/* The frontend name (client name) as seen by other MIDAS clients   */
const char *frontend_name = "largefe";

/* The frontend file name, don't change it */
const char *frontend_file_name = __FILE__;

/* frontend_loop is called periodically if this variable is TRUE    */
BOOL frontend_call_loop = TRUE;

/* a frontend status page is displayed with this frequency in ms */
INT display_period = 0;

/* maximum event size produced by this frontend */
INT max_event_size = 10000;

/* maximum event size for fragmented events (EQ_FRAGMENTED) */
INT max_event_size_frag = 5 * 1024 * 1024;

/* buffer size to hold events */
INT event_buffer_size = 10 * 10000;

/* whether the values in EQUIPMENT struct override ODB values */
BOOL equipment_common_overwrite = FALSE;


System Function Declarations

These lines declare the pre-defined system functions which should be present.

INT frontend_init();
INT frontend_exit();
INT begin_of_run(INT run_number, char *error);
INT end_of_run(INT run_number, char *error);
INT pause_run(INT run_number, char *error);
INT resume_run(INT run_number, char *error);
INT frontend_loop();

Readout Function Declarations

Following the previous group is a second group of declarations, which define the readout functions. These depend on the defined equipments, and run when the respective equipment is triggered. In this example, one equipment will be defined, so there is one declaration. The user functions will be described in detail in later sections.

INT read_large_event(char *pevent, INT off);

If using an interrupt, callback function prototypes are also included

extern void interrupt_routine(void);
void register_cnaf_callback(int debug);

Equipment List

This list of structs defines the behaviour of your frontend (e.g. periodic or polled equipment). See Equipment List for full documentation of the entries.

Note that these are the default values for each equipment (used the very first time a frontend is run). If equipment_common_overwrite is FALSE, then some of the values will subsequently be read from the ODB instead. If equipment_common_overwrite is TRUE, then the ODB will be updated with the coded values each time the program runs.

EQUIPMENT equipment[] = {

  {"large",                   /* equipment name */
    {3, 0,                    /* event ID, trigger mask */
     "SYSTEM",                /* event buffer */
     EQ_PERIODIC | EQ_FRAGMENTED,     /* equipment type */
     0,                       /* event source */
     "MIDAS",                 /* format */
     TRUE,                    /* enabled */
     RO_ALWAYS,               /* read when running and on transitions */
     2000,                    /* read every 2 sec */
     0,                       /* stop run after this event limit */
     0,                       /* number of sub events */
     0,                       /* log history */
     "", "", ""},
     read_large_event,        /* readout routine */
     NULL, NULL,              /* keep null */
     NULL,                    /* init string */
  },

  {""}
};

In the case of a polled equipment, the struct would be of the form:

   EQUIPMENT equipment[] = {
     { "Trigger",            // equipment name
       {
         ...
         EQ_POLLED,          // equipment type
         ...
         500,                // poll for 500ms 
         ...
         "", "", "",},
         read_my_event,    // readout routine 
      ...


In the case of a periodic equipment, the struct would be of the form:

   EQUIPMENT equipment[] = {
     { "Scaler",           // equipment name
        {    
           ...
           EQ_PERIODIC     // equipment type
           ...
           10000,          // period (read every 10s)
           ...
           "", "", "",},
      read_my_event,   // readout routine 
      ...


Sequence of Operations in the frontend

The following table shows the sequence of operations of the Frontend System functions. These functions must be implemented in the user's code (but may be as simple as just returning SUCCESS if the function is not relevant for your use case). These functions are called by mfe.cxx at the appropriate time. The System Transition functions are associated with a particular Run Transition as shown below:

System Function System Transition Function Associated Transition Action
frontend_init() Runs once after system initialization, before Equipment registration.
begin_of_run() TR_START Runs after system statistics reset at each begin-of-run request.
pause_run() TR_PAUSE Runs at each pause-run request.
resume_run() TR_RESUME Runs at each resume-run request.
end_of_run() TR_STOP Runs at each end-of-run request.
frontend_exit() Runs once before any Slow Control Equipment exit


Each defined Equipment has the option to force itself to run at individual transition times (see Equipment ReadOn Flag), so that its equipment function will be called on a certain transition (or combination of transitions).

The system transition functions all run prior to the equipment functions. This gives the system the chance to take basic action on the transition request (e.g. enable/disable interrupt) before the equipment runs. All the transition routines run with a Transition Sequence number of 500 (the default). This allows users to add additional functions in the frontend that will run before or after any of the transitions (such as a prestart() or a poststop() function). See Run Transition Priority for more information.

Function frontend_init

No parameters.

This function runs once only at the application startup. Users may perform hardware checking, loading/setting of global variables, mapping of required ODB structures (see experim.h include file), setting up hot-links etc. in frontend_init(), e.g.


INT frontend_init()
{
  set_equipment_status(equipment[0].name, "Initializing...", "yellow");
  ....                  
  set_equipment_status(equipment[0].name, "OK", "green");

  return SUCCESS;
}


Reporting Equipment Status

If running with the webserver mhttpd, a frontend can send an update to the Status Page, to report on its progress, using the function set_equipment_status() (see above example). This is useful when hardware can take a long time to respond.


Function begin_of_run

Parameters:

  • INT run number provides the number of the current run being started
  • char * error can be used for returning a message to the system. This message string will be logged into the midas.log file (see Message System.

This function is called every time a run start transition occurs, i.e. at begin-of-run. It allows the updating of user parameters, and the loading/setup/clearing of hardware. At the exit of this function, the acquisition should be armed and ready to test the interrupt (if used), e.g.


INT begin_of_run (INT runnumber, char * error)
{
  // Read/validate some settings from the ODB (and return FE_ERR_ODB if there's a problem).
  // Apply them to the hardware (and return FE_ERR_HW if there's a problem).
  // etc...
  return SUCCESS;
}

Functions pause/resume_run

Parameters:

  • INT run number provides the number of the current run being paused/resumed.
  • char * error can be used for returning a message to the system. This message string will be logged into the midas.log file (see Message System.

These two functions are called upon "Pause" and "Resume" command respectively. Any code relevant to the upcoming run state can be included,e.g.


INT pause_run (INT run_number, char * error)
{
  disable_trigger();
  // Disable interrupt
  inRun = 0;
  mvme_write_value(myvme, VLAM_BASE+4, inRun);
  return SUCCESS;
}
                           //
INT resume_run (INT run_number, char * error)
{
  enable_trigger();
  inRun = 1;
  mvme_write_value(myvme, VLAM_BASE+4, inRun);
  return SUCCESS;
}

Function end_run

Parameters:

  • INT run number provides the number of the current run being ended.
  • char * error can be used for returning a message to the system. This message string will be logged into the midas.log file (see Message System.

This function is called at every "stop run" transition. It provides the opportunity to disable the hardware, e.g.

INT end_of_run(INT run_number, char *error)
{
  // Stop the hardware.
  // etc...
  return SUCCESS;
}

Function frontend_exit

Parameters: none

The function runs when the frontend program is shut down. Can be used to release any locked resources like memory, communications ports etc. e.g.

 function frontend_exit()
 {
    mvme_close(gVme);
    return;
 }

Function frontend_loop

Parameters: none

If frontend_call_loop is set to TRUE, this routine is called when the frontend is idle and at least once between every event. You could use it for example to check if there has been a timeout from hardware.

...
BOOL frontend_call_loop = TRUE;
...
                                
INT frontend_loop()
{
  // Implement any code that needs to be run very frequently
  return SUCCESS;
}

Event Types and Triggers

The frontend supports several different types of event trigger. The event trigger type is specified by the Equipment Flag in the Equipment Declaration. Common event types are "polled events" where the Equipment Flag is EQ_POLLED, "interrupts events" where the Flag is EQ_INTERRUPT, and "periodic events" where the Flag is EQ_PERIODIC. The name of the associated readout routine is specified in the Equipment Declaration for each event type.

Polled and interrupt events (see Event Types) require several extra functions to handle the hardware that periodic events do not require. These are described below.

Note that each frontend may contain:

  • zero or one polled equipments
  • zero or one interrupt equipments
  • any number of periodic equipments

Function poll_event

Parameters:

  • INT source
  • INT count
  • BOOL test

If the Equipment Type is EQ_POLLED, the poll_event() routine will be called as often as possible over the corresponding poll time (e.g. 500ms) given by each polling equipment.

The user must provide suitable code in the routine poll_event(), e.g. reading a register from a VME module to see if any data is available

INT poll_event(INT source, INT count, BOOL test)
{
   /* Polling routine for events. Returns TRUE if event  is available. If test equals TRUE, don't return. 
      The test flag is used to time the polling 
    */
    int i;
    int lam = 0;
                                //
    for (i = 0; i < count; i++, lam++) {
      lam = vmeio_CsrRead(myvme, VMEIO_BASE);
      if (lam)
        if (!test)
          return lam;
    }
    return 0;
 }

An event readout routine must also be provided by the user.


Function interrupt_configure

  • INT cmd
  • INT source
  • PTYPE adr

If the Equipment Type is EQ_INTERRUPT, an interrupt configuration routine called interrupt_configure() must be provided by the user. The interrupt configuration routine has the following declaration:

/*-- Interrupt configuration --------------------------*/
INT interrupt_configure(INT cmd, INT source, PTYPE adr)
{
 int vec = 0;
 switch (cmd) 
 {
   case CMD_INTERRUPT_ENABLE:
     if (inRun) mvme_write_value(myvme, VLAM_BASE+4, 0x1);
     break;
                                     //
   case CMD_INTERRUPT_DISABLE:
     if (inRun) mvme_write_value(myvme, VLAM_BASE+4, 0x0);
     break;
                                     //
   case CMD_INTERRUPT_ATTACH:
     mvme_set_dmode(myvme, MVME_DMODE_D32);
     mvme_interrupt_attach(myvme, INT_LEVEL, INT_VECTOR, 
               (void *)adr, &myinfo);
     mvme_write_value(myvme, VLAM_BASE+0x10, INT_VECTOR);
     vec = mvme_read_value(myvme, VLAM_BASE+0x10);
     printf("Interrupt Attached to 0x%x for vector:0x%x\n",
                    adr, vec&0xFF);
     break;
                                     //
   case CMD_INTERRUPT_DETACH:
     printf("Interrupt Detach\n");
     break;
  }
  return SUCCESS;
}

Under the four commands listed above, the user must implement the hardware operation needed to perform the requested action. In the Midas drivers directory examples can be found of such an interrupt code for CAMAC. See source code such as hyt1331.c,ces8210.c

An event readout routine must also be provided by the user in the frontend.

Event Readout routine

An event readout routine is required for all equipment, and is responsible for sending the actual data to midas. The framework calls the event readout routine whenever an equipment has been triggered (e.g. periodicially, or because poll_event() returned TRUE for a polled equipment etc). The function is of the form

INT function_name ( char *pevent ... )
{
  INT event_size;
  ........  // read data from hardware
  ........  // pack into banks depending on format
  ........
  return (event_size);
}

where the first argument of the readout function (pevent) provides the pointer to the newly constructed event, and points to the first valid location for storing the data.

NOTE
  • The return value is the event size, and must be the number of bytes collected in this function. This is different to almost every other function in midas (where the return value is a status code).
  • You can return 0 if you've decided you don't actually want to write this event.
  • The event serial number will be incremented by one for every call to the readout routine, as long as the returned size is non-zero.


General readout function

A readout function is needed to send out data. This is done using one of the supported event structures usually "MIDAS" format data banks. The bank format ("MIDAS" in the example above) is declared in the Equipment List Parameters Format field.

An example of a scaler readout routine read_scaler_event() where the data is read out into MIDAS data banks is shown below.

INT read_large_event(char *pevent, INT off)
{
  DWORD *pddata;
  /* init bank structure */
  bk_init32(pevent);
  /* create bank (bank names must be 4 chars long) */
  bk_create(pevent, "BIGG", TID_DWORD, (void **) &pddata);
  /* fill data (just dummy values in this case) */
  memset((char *) pddata, 0x0000, 100);
  pddata += 1000000;
  memset((char *) pddata - 100, 0xFFFF, 100);
  /* close the bank */
  bk_close(pevent, pddata);
  /* return the number of bytes we wrote */
  return bk_size(pevent);
}

Some other examples of event readout routines in this document are

Many other examples of readout routines can be found in the frontend templates in the MIDAS package.

Polled or Interrupt readout routine

In the case of a Polled or Interrupt event, the content of the memory location pointed to by pevent (see Event Readout routine) prior to its use in the readout function, contains the interrupt source bitwise register. This feature can be exploited in order to identify which hardware module has triggered the readout when multiple interrupts have been assigned to the same readout function.

The examples below show a VME interrupt source for a given equipment. Depending whether USE_INT is defined, the Equipment will either use a Polled or an Interrupt mechanism. The Equipment declaration is of the form:

EQUIPMENT equipment[] = {
              //
   {"Trigger",  /* equipment name */
      ...
#ifdef USE_INT
      EQ_INTERRUPT, /* equipment type */
#else
      EQ_POLLED,    /* equipment type */
#endif
  /* interrupt source: crate 0, all stations */
      LAM_SOURCE(0, 0x0),
      ....
      "", "", "",
    },
    read_trigger_event, /* readout routine */
    NULL, NULL,
    trigger_bank_list,
}
 

Note that the LAM_SOURCE macro simply codes the parameters into a bitwise register.

The readout routine would contains code such as

INT read_trigger_event(char *pevent, INT off)
{
#if defined VADC0_CODE
  DWORD  *pdata;
#endif
                       //
#if defined VADC0_CODE
  /* read ADC0 data */
  v792_EvtCntRead(myvme, VADC0_BASE, &evtcnt);
  ........
  /* Read Event */
  v792_EventRead(myvme, VADC0_BASE, pdata, &nentry);
  ........
  v792_DataClear(myvme, VADC0_BASE);
#endif
                      //
  ........
  return (size);
}

The data will be packed into banks as described for the general readout function above. The example $MIDASSYS/examples/Triumf/c/fevmemodules.c contains a complete example of read_trigger_event().


Manual Trigger

Another type of frontend event trigger supported is the "manual trigger", where the Equipment Flag is EQ_MANUAL_TRIGGER. This flag means that an event can be triggered through the URL ?cmd=Trigger/<equipment_name> (e.g. http://my.host:8080/?cmd=Trigger/MyEquipmentName). If you add this URL to the /Alias part of the ODB, then a link will appear on midas webpages that can be clicked to trigger an event.

In some cases, the same readout code may be used for two types of event: a manual trigger and (say) a poll event. It is possible to determine whether the readout of an event was triggered by a manual trigger or a regular trigger by adding a call to the event header Macro DATA_SIZE in the readout routine:

 flag = DATA_SIZE(pevent);

If the result is

 *  flag = 0 normal call
 *  flag = 1 manual trigger

It is also possible for a backend MIDAS client (such as an analyzer or custom data archiver) to trigger a manual trigger event. The client controls when an event is sent by means of a function that requests an event by triggering the event sending mechanism with a RPC call.

With a frontend Equipment declaration of a manually triggered event of the form:

   { "Histo",             /* equipment name */
      2, 0,                 /* event ID, trigger mask */
      "SYSTEM",             /* event buffer */
      EQ_MANUAL_TRIG,     /* equipment type */
      0,    
      .......
   }
   

the code fragment to manually trigger this event is:

 int main(unsigned int argc,char **argv)
 {
     .......
     bm_request_event(hBufEvent, 2, TRIGGER_ALL, GET_ALL, &request_id, process_event_TD);
     .......
 }

When it is time to save the data during the run, the function below is called:

BOOL trigger_histo_event(void)
{
  HNDLE hconn;
  BOOL event_triggered;
                           //
  event_triggered = FALSE;
  ...................
  if (run_state == STATE_RUNNING) {   // Check the frontend client exists
       if( cm_exist(ClientName,TRUE))  {    
           status = cm_connect_client (ClientName, &hconn);
           if(status != RPC_SUCCESS)
               cm_msg(MERROR,"trigger_histo_event","Cannot connect to frontend \"%s\" (%d)",
                      ClientName,status);
           else  {     // successfully connected to frontend client
               status = rpc_client_call(hconn, RPC_MANUAL_TRIG, 2); // trigger a histo event
               if (status != CM_SUCCESS)
                   cm_msg(MERROR,"trigger_histo_event","Error triggering event from frontend (%d)",status);
               else {  // successfully triggered event
                   event_triggered=TRUE;
                   status =cm_disconnect_client(hconn, FALSE);
                   if (status != CM_SUCCESS)
                       cm_msg(MERROR,"trigger_histo_event","Error disconnecting client (%d)",status);
               }
           }
       }
       else
           cm_msg(MERROR,"trigger_histo_event","Frontend client %s not running (%d)",
                  ClientName,status);
   } 
   return(event_triggered);
}

Deferred Transition

This option permits the user to postpone any transition issued by any requester until some condition is satisfied. For example:

  • It may not be advisable to pause or stop a run until some hardware has turned off a particular valve.
  • The start of the acquisition system should be postponed until the beam rate has been stable for a given period of time.
  • While active, a particular acquisition system should not be interrupted until the "cycle" is completed.

In these examples, any application having access to the state of the hardware can register to be a "transition Deferred" client. The MIDAS system will then catch any transition request and postpone the trigger of such a transition until the condition is satisfied.

The Deferred transition requires 3 steps for setup

  1. Register for the deferred transition
  2. Provide a callback function to serve the deferred transition
  3. Implement the condition code

Note that you should only have ONE frontend that defines a deferred transition (as this niche feature that was implemented with this assumption in mind...).

The following example demonstrates this process:

 BOOL transition_PS_requested=FALSE; // global
                                             //
 INT frontend_init()
 {
   // register for deferred transition
                                             //
   cm_register_deferred_transition(TR_STOP, wait_end_cycle);
   cm_register_deferred_transition(TR_PAUSE, wait_end_cycle);
   ...  
 }
/*
*/ 
 //-- Deferred transition callback
 BOOL wait_end_cycle(int transition, BOOL first)
 {
   if (first) {
     transition_PS_requested = TRUE;
     return FALSE;
   }
                       //
   if (end_of_mcs_cycle){
     transition_PS_requested = FALSE;
     end_of_mcs_cycle = FALSE;
     return TRUE;
   }
   else
     return FALSE;
 }
/*
*/
 INT read_mcs_event(char *pevent, INT offset)
 {  
     ...     // read out data at end of cycle
     ...
     end_of_mcs_cycle = TRUE; // end of cycle 
                              //
     if (!transition_PS_requested)
        start_cycle(); // start a new cycle 
     return bk_size(pevent);
  }


In the example above,

  • The frontend code is registered for PAUSE and STOP using cm_register_deferred_transition(). The second argument wait_end_cycle is the declaration of the callback function.
  • The callback function wait_end_cycle will be called as soon as the transition is requested with the Boolean flag first set to TRUE.
  • By setting transition_PS_requested TRUE , the user will be provided with the acknowledgment of the transition request.
  • By returning FALSE from the callback, the transition is prevented from occurring.
  • As soon as the user condition is satisfied (end_of_mcs_cycle = TRUE), the return code in the callback will be set to TRUE and the requested transition will be issued.

While the transition is Deferred, the odb key /runinfo/Requested transition will contain the transition code, and the d mhttpd webserver main status page will indicate that a deferred transition is in progress.

Once in deferred state, an odbedit override command can be issued to force the transition to happen,

>odbedit
odb> stop now    (or "start now")

or /runinfo/Requested transition can be set to 0. The transition will then take place on the next stop or start command.

Compilation using CMake

Here is a "minimal" CMakeLists.txt file that can be used to compile a user-written frontend (called "myfe" in this case) that uses the mfe framework.

 cmake_minimum_required(VERSION 3.0)
 project(myfe)
 
 # Check for MIDASSYS environment variable
 if (NOT DEFINED ENV{MIDASSYS})
   message(SEND_ERROR "MIDASSYS environment variable not defined.")
 endif()
 
 set(CMAKE_CXX_STANDARD 11)
 set(MIDASSYS $ENV{MIDASSYS})
 
 if (${CMAKE_SYSTEM_NAME} MATCHES Linux)
   set(LIBS -lpthread -lutil -lrt)
 endif()
 
 # Define the executable to be built, and the source code files
 add_executable(myfe.exe myfe.cxx)
 
 # Directories to search for 
 target_include_directories(myfe.exe PRIVATE ${MIDASSYS}/include)
 
 # Libraries to link to
 target_link_libraries(myfe.exe ${MIDASSYS}/lib/libmfe.a ${MIDASSYS}/lib/libmidas.a ${LIBS})

One could then build the executable using the folllowing commands:

 mkdir build
 cd build
 cmake ..
 make

Some older operating systems may require the "cmake3" command to be used instead of "cmake".

Using mfed to reduce the amount of boiler-plate code

You can include mfed.h and compile against mfed.cxx to reduce the amount of boiler-plate code that needs to be written.

In particular:

  • You only need to define the frontend_name and frontend_file_name globals (not display_period etc)
  • You only need to declare your event readout functions (not the system functions)
  • You still need to define your EQUIPMENT structs
  • You need to define frontend_init(), from which you can call install_poll_event(), install_begin_of_run() etc to use your transition functions. If you don't need a pause_run() function, you don't need to declare/define it!
  • It does not support interrupt routines

An example can be found in $MIDASSYS/examples/experiment/frontend.cxx.

The full list of functions you can call in your frontend_init() are:

install_poll_event
Install a function which gets called to check if a new event is available for equipment of type EQ_POLLED.
install_frontend_exit
Install a function which gets called when the frontend program finishes.
install_begin_of_run
Install a function which gets called when a new run gets started.
install_end_of_run
Install a function which gets called when a new run gets stopped.
install_pause_run
Install a function which gets called when a new run gets paused.
install_resume_run
Install a function which gets called when a new run gets resumed.
install_frontend_loop
Install a function which gets called inside the main event loop as often as possible. This function gets all available CPU cycles, so in order not to take 100% CPU, this function can use the ss_sleep(10) function to give up some CPU cycles.

When compiling with cmake, your incantation for the executable would change:

# Without mfed (regular frontend):
add_executable(frontend frontend.cxx)

# With mfed:
add_executable(frontend frontend.cxx ${MIDASSYS}/src/mfed.cxx)

Minimal example without mfed

  #include <stdio.h>
  #include <stdlib.h>
  #include "midas.h"
  #include "mfe.h"
  
  /*-- Globals -------------------------------------------------------*/
  /* The frontend name (client name) as seen by other MIDAS clients   */
  const char *frontend_name = "largefe";
  
  /* The frontend file name, don't change it */
  const char *frontend_file_name = __FILE__;
  
  /* frontend_loop is called periodically if this variable is TRUE    */
  BOOL frontend_call_loop = TRUE;
  
  /* a frontend status page is displayed with this frequency in ms */
  INT display_period = 0000;
  
  /* maximum event size produced by this frontend */
  INT max_event_size = 10000;
  
  /* maximum event size for fragmented events (EQ_FRAGMENTED) */
  INT max_event_size_frag = 5 * 1024 * 1024;
  
  /* buffer size to hold events */
  INT event_buffer_size = 10 * 10000;
  
  /*-- Function declarations -----------------------------------------*/
  
  INT frontend_init();
  INT frontend_exit();
  INT begin_of_run(INT run_number, char *error);
  INT end_of_run(INT run_number, char *error);
  INT pause_run(INT run_number, char *error);
  INT resume_run(INT run_number, char *error);
  INT frontend_loop();
  INT read_large_event(char *pevent, INT off);
  
  /*-- Equipment list ------------------------------------------------*/
  
  #undef USE_INT
  BOOL equipment_common_overwrite = FALSE;
  
  EQUIPMENT equipment[] = {
  
     {"large",                 /* equipment name */
        {3, 0,                    /* event ID, trigger mask */
        "SYSTEM",                /* event buffer */
        EQ_PERIODIC | EQ_FRAGMENTED,     /* equipment type */
        0,                       /* event source */
        "MIDAS",                 /* format */
        TRUE,                    /* enabled */
        RO_ALWAYS,               /* read when running and on transitions */
        2000,                    /* read every 2 sec */
        0,                       /* stop run after this event limit */
        0,                       /* number of sub events */
        0,                       /* log history */
        "", "", ""},
        read_large_event,        /* readout routine */
        NULL, NULL,              /* keep null */
        NULL,                    /* init string */
        },
  
     {""}
  };
  
  INT frontend_init()
  {
     return SUCCESS;
  }
  
  INT frontend_exit()
  {
     return SUCCESS;
  }
  
  INT begin_of_run(INT run_number, char *error)
  {
     return SUCCESS;
  }
  
  INT end_of_run(INT run_number, char *error)
  {
     return SUCCESS;
  }
  
  INT pause_run(INT run_number, char *error)
  {
     return SUCCESS;
  }
  
  INT resume_run(INT run_number, char *error)
  {
     return SUCCESS;
  }
  
  INT frontend_loop()
  {
     // Do something very frequently
     return SUCCESS;
  }
  
  INT poll_event(INT source, INT count, BOOL test)
     return 0;
  }
  
  INT interrupt_configure(INT cmd, INT source, PTYPE adr)
  {
     switch (cmd) {
     case CMD_INTERRUPT_ENABLE:
        break;
     case CMD_INTERRUPT_DISABLE:
        break;
     case CMD_INTERRUPT_ATTACH:
        break;
     case CMD_INTERRUPT_DETACH:
        break;
     }
     return SUCCESS;
  }
  
  
  INT read_large_event(char *pevent, INT off)
  {
     DWORD *pddata;
  
     /* init bank structure */
     bk_init32(pevent);
  
     bk_create(pevent, "BIGG", TID_DWORD, (void **) &pddata);
     memset((char *) pddata, 0x0000, 100);
     pddata += 1000000;
     memset((char *) pddata - 100, 0xFFFF, 100);
     bk_close(pevent, pddata);
  
     return bk_size(pevent);
  }

Minimal example with mfed

Note that you only need boiler-plate for the features you're actually going to use.

  #include <stdio.h>
  #include <stdlib.h>
  #include "midas.h"
  #include "mfe.h"
  
  /*-- Globals -------------------------------------------------------*/
  /* The frontend name (client name) as seen by other MIDAS clients   */
  const char *frontend_name = "largefe";
  
  /* The frontend file name, don't change it */
  const char *frontend_file_name = __FILE__;
  
  
  /*-- Function declarations -----------------------------------------*/
  INT frontend_init();
  INT my_frontend_loop();
  INT read_large_event(char *pevent, INT off);
  
  /*-- Equipment list ------------------------------------------------*/
  
  #undef USE_INT
  BOOL equipment_common_overwrite = FALSE;
  
  EQUIPMENT equipment[] = {
  
     {"large",                 /* equipment name */
        {3, 0,                    /* event ID, trigger mask */
        "SYSTEM",                /* event buffer */
        EQ_PERIODIC | EQ_FRAGMENTED,     /* equipment type */
        0,                       /* event source */
        "MIDAS",                 /* format */
        TRUE,                    /* enabled */
        RO_ALWAYS,               /* read when running and on transitions */
        2000,                    /* read every 2 sec */
        0,                       /* stop run after this event limit */
        0,                       /* number of sub events */
        0,                       /* log history */
        "", "", ""},
        read_large_event,        /* readout routine */
        NULL, NULL,              /* keep null */
        NULL,                    /* init string */
        },
  
     {""}
  };
  
  INT frontend_init()
  {
     install_frontend_loop(my_frontend_loop);
  
     // Can also do install_begin_of_run() etc...
  
     return SUCCESS;
  }
  
  INT my_frontend_loop()
  {
     // Do something frequently
     return SUCCESS;
  }
  
  INT read_large_event(char *pevent, INT off)
  {
     DWORD *pddata;
  
     /* init bank structure */
     bk_init32(pevent);
  
     bk_create(pevent, "BIGG", TID_DWORD, (void **) &pddata);
     memset((char *) pddata, 0x0000, 100);
     pddata += 1000000;
     memset((char *) pddata - 100, 0xFFFF, 100);
     bk_close(pevent, pddata);
  
     return bk_size(pevent);
  }