Page tree

DOOCS Server For Dummies

by Olaf Hensler, Gerhard Grygiel, Lars Fröhlich, Yordanka Janik - MCS

This document describes the first steps to write a DOOCS server application and explains all the relevant steps. This covers a hardware device server called “clean_template”.

Design Idea

The main design idea of DOOCS is to model a certain piece of hardware into a software class. That means, one models an ion-pump, a magnet or one BPM. Several objects of this class are created
during start-up of this server by adding them to the servers configuration file. In addition, it may possible to model a specific task, like an automation  problem.

Preamble

A DOOCS server application may run on various computer architectures. This page describes the work-flow for a Linux-based Ubuntu 20 platform. The development host for this is named "doocsdev20". 

Preparation

Log on to one of the development node for Linux-based development:

ssh doocsdev20 

Create a doocs directory in your home directory:

mkdir doocs.git
cd doocs.git

One may check the DOOCSARCH environment variable with:

echo $DOOCSARCH

It should be:

DOOCSARCH == Ubuntu-20.04-x86_64 # for Linux like MicroTCA systems
DOOCSARCH == Darwin-x86_64 # for macOS
DOOCSARCH == Raspbian-9-armv7l # for a Raspberry Pi system

For additional information please refer to the DOOCS website: doocs-web.desy.de → Documentation → Code Development.

git clone git@doocs-git:doocs/Ubuntu-20.04-x86_64 doocs/Ubuntu-20.04-x86_64

Checkout the existing example from the Git repository as explained below.

Git Check-out / Check-in

In order to work on a DOOCS server application, it is preferred that the server code already exists in the Git versioning system. If you start with a brand new server application, ask for an empty Git repository by sending an e-mail to doocs-rt@desy.de, specifying the Git path and a short description what a server is about. After an admin has notified you the server repository exists now in Git and you can clone it. A new server repository is completely empty and needs to be populated with source code files.

In our example the clean_template already exists in Git, so you can clone without the need to register it. 

E.g.:

cd ~/doocs.git
git clone git@doocs-git:doocs/server/example/clean_template   doocs/server/example/clean_template

Now you can access the directory locally:

cd doocs/server/example/clean_template

And when ready you can commit the changes:

~/doocs.git/doocs/server/example/clean_template$ git commit -m "Reasonable message to explain this commit"

Class design

Having the preparation finished, this source code can be adapted to the given hardware. The main design idea of a DOOCS server is, that this EqFct class should reflect the properties of the hardware. The EqFct class defines a location and gives it already about 15 standard properties. The programmer has to decide, which properties are needed in addition. These properties are picked together from a long list of data functions, called D_fct. Do I need a floating point reading (D_float) of a pressure, do I want to archive this value use D_floathist, do I have a bit register use D_status. Properly I need to switch something on or off, so D_bit is needed and has to be overloaded. 

Usually the hardware specifies the communication to use. DOOCS offers SEDAC (D_sedac), ProfiBus (D_profi), CANbus (D_can), Ethernet (D_DOOCSenet) or PCIe and VME access. There are many more, for a complete list please check the DOOCS manual pages. The D_DOOCSdapi library should be used in the case of a DOOCS Middle Layer server. In case, one wants to get data from the DAQ shared memory, the D_DOOCSddaq library is provided. The programmer has to declare these D_fct classes in the eq_dummy.h file and give it the DOOCS network name in the clean_template_rpc_server.cc file.

Most important methods

The constructor

Inside the constructor all the DOOCS properties with its network names are defined. At start-up of the DOOCS server here all the D_fct objects are created and initialized with the values from clean_template_server.conf file.

The init method

The init method is called at start up time for every location. It can be used to initialize the hardware during start-up of the server. All the settings from the configuration file clean_template_server.conf are already known here. The .conf file needs to have the name  – “eq_fct_name” and the unique number -“eq_fct_type” of the location which needs to be created.

The update method

Usually DOOCS server are using the timer of the operating system to do frequent readout of the hardware. When this timer event occurs, a function is called and loops over all locations. So all the work for one location is done inside update.

The online method

The online method is called, when sending a “1” (type bool) to the property SET.ONLINE. The idea of this method is to find out by a hardware call, e.g. SEDAC, if a certain device is available.

The interrupt_usr1 method

This method is call by an external signal, e.g. from the timing system, to be in synch with some hardware. Like the update method, interrupt_usr1 is called once per location.

Other methods

Most of these methods are usually empty, but can stay to be part of the DOOCS server or be removed if not used.

eq_init_prolog ()
Called once before the init loop. May be used for global initialization, which are common to all
devices e.g. open a communication.

eq_init_epilog ()
Called once after the init loop.

post_init_prolog ()
Called once before the post_init loop.

post_init_epilog ()
Called once after the post_init loop.

refresh_prolog ()
Called once before the update loop.

refresh_epilog ()
Called once after the update loop.

interrupt_usr1_prolog(int)

interrupt_usr1_epilog(int)
Called, when SIGUSR1 interrupt is used

interrupt_usr2_prolog(void)
interrupt_usr2_epilog(void)
Called, when SIGUSR2 interrupt is used

post_init_prolog(void)
post_init_epilog(void)

cancel(void)
Called for every location, when the DOOCS server exits

eq_cancel(void)
Called, when the DOOCS server exits

Sending commands

Usually one needs to send something to the hardware to set a register or to switch a device on or off. To do it from the network one needs to overload one of the d_fct. For instance, if one wants to send a float value to the hardware, the D_float class will be overloaded. The method to overload is the D_float::set_value() method. Its sets the internal float value and one has to add software to set the hardware.

// Declaration of the class
class D_myfloat : public D_float {
public:
	D_myfloat ( const std::string &pn, EqFct* efp ) :
	D_float( pn, (EqFct*)efp ), eq_fct_(efp) {}
	void set_value ( float f );
};
// Overloaded method
void D_myfloat::set_value ( float f )
{
	D_float::set_value ( f ); // set the local value
	//
	// Do your hardware access here
}

For a more advanced example of a D_bit overloading, check the attached source code.

Compile and run

In order to compile your DOOCS server, please define your environment variable DOOCSARCH and then just type make inside the source directory. On Linux define DOOCSARCH to Ubuntu-20.04-x86_64

cd ~/doocs.git/doocs/server/example/clean_template
make


This make run will create an object directory called :

~/doocs.git/doocs/Ubuntu-20.04-x86_64/obj/server/example/clean_template for Linux


After successful compilation all .o files and the actual DOOCS server binary are in this directory. To run the server executable a configuration file needs to be added or copied to the above directory where the server package will be installed.

Installation on Linux

To create a package on Ubuntu Linux, do the following steps :
Login to doocsdev20 with your personal account

$cd ~/doocs.git/doocs/server/example/clean_template

Create a file called RPC_LIBNO (if not yet exisitng) and insert the RPC library number (SVR.RPC_NUMBER e.g.
610498009). For new servers a new number must be chosen and registered under DOOCS ENS
Database, which keeps track of all the registered RPC library numbers. Request a new number
for your server at doocs-rt@desy.de
Type:

makeDdeb -I

Now you have a Debian package on the central package repository, which may installed on one
or several front-end computer.

The above command is called only for the very first creation of the Debian package.
In the case of out clean_template example, the debian package alredy exists, so for subsequent versions call: 

~/doocs.git/doocs/server/example/clean_template$ make package

To install this package on your front-end computer, login with your personal account and do the
following steps :

ssh clean_template_computer (e.g. ssh doocsdev20 or ssh xfelml2)

Login with your own account, sudo rights are required:

/export/doocs/server$ sudo apt update
/export/doocs/server$ sudo apt install doocs-clean-template-server

The server is now installed on the front-end computer. Don't forget to modify/exchange the default .conf and RPC_LIBNO files.

Configure and start the server 

The following command will open the .conf file for editing in a text editor:

cd ~/export/doocs/server/clean_template_server
geany clean_template_server.conf

Alternatively, if geany is not available, you can edit the .conf file with other editor, e.g. with "vi".

For a first local test, add/change the following line somewhere behind SVR.NAME: into the clean_template _server.conf to:

SVR.RPC_NUMBER: 610498009

This allows you to run the DOOCS server without a name service (ENS) entry. To start the server run:

/export/doocs/server/clean_template_server$ clean_template_server &

To see it from the client application, start our DOOCS browser program RPC_TEST tool and serach for the  facility TEST.DOOCS, and device LOCALHOST_610498009.  Now you should see the location list from your clean_template_server.conf file.

Appendix

Example header file:

// file eq_example.h
//
// Test Eq functions class
//
// This is a general test server. The server get the name of the
// test file and for the history file at compile-time as define parameter
// inside the Makefile
//
//
// Olaf Hensler, - MCS4 -
//
// last update:
// 04. Jul. 2018
//
#ifndef eq_example_h
#define eq_example_h
#include"eq_fct.h"
#define CodeExample 10 // eq_fct_type number for the .conf file

class D_mybit : public D_bit {
	EqFctExample* eq_fct_; 			// pointer to the location
public:
	D_mybit ( const std::string &pn, u_char b, u_short* sp, EqFctExample* efp ) :
			D_bit( pn, b ,sp, (EqFct*)efp ), eq_fct_(efp) {}

	void set_value ( int );
	void read() {} 					// does NOT read from .conf file at start up
};
class EqFctExample : public EqFct {

private:
	D_floathist value_;
	D_int int_value_;
	D_status status_;
	D_mybit bit0_; 					// overloaded bit
	D_bit bit1_;

public:
	EqFctExample ( );
	void update ();
	void init (); 					// started after creation of all Eq's
	int fct_code() { return CodeExample; };
};
#endif

Example source file:

// file eq_example.cc
//
// Example Eq function class
//
// This is a general example server used for the documentation
// "DOOCSserver for Dummies"
//
// Olaf Hensler, - MCS4 -
//
// last update:
// 04. Juli. 2018
//
//
#include"eq_example.h"
#include"eq_fct_errors.h"

EqFct* example_fct;

char* object_name = "ExampleServer";

int EqFctExample::conf_done = 0;

EqFctExample::EqFctExample ( ) :
	EqFct ("NAME = location" ),
	value_ ("FLOATDATA test value with history", this, "TEST" ),
	int_value_ ("INTDATA integer test value", this ),
	status_ ("REGISTER a hardware refister 16bits", this ),
	bit0_ ("BIT0 first bit of REGISTER", 0, &status_.stat_, this),
	bit1_ ("BIT1 second bit of REGISTER", 1, &status_.stat_, this)
{
}

void eq_init_prolog () {} 			// called once before init of all EqFct's

//
// The init() method is call for every location during startup of the server
// Initialization of the hardware may be done here
//

void EqFctExample::init ( )
{
	std::cout << "Inside init(), Location name : " << name_.value() << std::endl;
}

void eq_init_epilog () {}			 // called once at end of init of all EqFct's

//
// used during startup of the server to create the locations
//
EqFct *
eq_create (int eq_code, void *)
{
	 EqFct* eqn;
 	switch (eq_code) {
 		case CodeExample:
 			eqn = new EqFctExample ();
			break;
	 default:
 		eqn = (EqFct *) 0;
 		break;
 	 }
 	 return eqn;
}

void refresh_prolog () {} 				// called before "update"

//
// This "update" method usually does the real work in a DOOCS server
// "update" is called frequently with the rate configured by SVR.RATE
// This method is called for every location, e.g. runs in a loop over all locations
// configured inside the .conf file
//
void EqFctExample::update ( )
{
	float fdata;
 	if( g_sts_.online() ) {
		//
		// do some hardware readout, e.g. SEDAC, Ethernet ...
		//
		if( !error ) {
 			std::string test;
			test = "10457.878";
			fdata = atof (test.c_str() );
			value_.set_value( fdata ); 				// does archiving automatically

			int idata = atof (test.c_str() );
			int_value_.set_value( idata );

 			set_error( no_error );
		}
		else {
 			fdata = value_.value();
			value_.set_value( fdata, sts_err ); 	// history with error flag
 			set_error( device_error ); // set error for this location
		}
 	}
 	else {
 		fdata = value_.value();
		value_.set_value( fdata, sts_offline ); 	// history with offline flag
 		set_error( offline );
 	}
}

void refresh_epilog () {} 							// called after "update"

void D_mybit::set_value ( int set_res )
{
	if( !eq_fct_->g_sts_.online() ) return; 		// do nothing, if this location is offline

 	D_bit::set_value(set_res); 						// set local bits
 	switch ( bit_no_ )
 	{
		case 0: // Bit 0
 		if( set_res ) {
				// some hardware IO
 		}
 		else {
				// some hardware IO
 		}
 		break;
		case 1: // Bit 1
 		break;
 	}
}

//
// The following methods are provided, when the server needs to use SIGUSR1 or SIGUSR2
// interrupts (from timing system)
//
void interrupt_usr1_prolog(int) {}
void interrupt_usr2_prolog(void) {}
void interrupt_usr1_epilog(int) {}
void interrupt_usr2_epilog(void) {}
void post_init_prolog(void) {}
void post_init_epilog(void) {}
void eq_cancel(void) {}

Example default .conf file:

# Conf file created at 15:15.08 9. Feb. 2018
# eq_fct_type's are defined in eq_fct_code.h
eq_conf:
oper_uid: -1
oper_gid: 403
xpert_uid: 0
xpert_gid: 0
ring_buffer: 50000
memory_buffer: 500
eq_fct_name: "TEST._SVR"
eq_fct_type: 1
{
SVR.NAME: "TEST._SVR"
STS: 0xc
STS.ERROR: 0
STS.NEWERROR: 1
STS.ERRORMASK: 0
STS.ONLINE: 1
ERROR.STR: 0 0 238 1518085698 "ok"
SYS_MASK: 0
FCT_CODE: 1
FCT_PANEL: ""
X_POS: 0
Z_POS: 0
Z_POS.STRING: ""
DEVICE.INFO: 0 0 0 0 "Device OK"
MESSAGE: ""
LAST_UPDATE: 1091026544 233 0 0
LAST_USR1: 0 0 0 0
SVR.ALIAS: 0
SVR.ARCFLUSH: 0
SVR.ARCFLUSH_B: 1
SVR.UPDATE: 1518185708
SVR.RATE: 1 0 0 0
SVR.RESIZE: 200
SVR.BCKCONF: 0 0 0 0 ""
SVR.FILE: "clean_template_server.conf"
SVR.DESC: ""
SVR.PROGRAMMER: "Autor"
SVR.XMLFILE: ""
SVR.ERRORLOG: "/export/doocs/server/clean_template_server/clean_template_server.log"
SVR.STORE.RATE: 500
SVR.STORE.AUTO: 10
SVR.HOST_NAME: "doocsdev20"
SVR.PROCESSNAME: "clean_template_server"
SVR.RPC_NUMBER: 610498009
SVR.STARTTIME: 1518085688
SVR.LIBINFO: "18.10.17"
SVR.LIBDATE: ""
SVR.WDADDR: ""
SVR.CONTR: 0x0
SVR.MUST_RUN: 0
SVR.STOP_SVR: 0
SVR.START_CMD: ""
SVR.RPC_CALL_TIME.COMMENT: "RPC-Call-Time"
SVR.RPC_CALL_TIME.EGU: 1 1 100000 0 "Counts"
SVR.RPC_CALL_TIME.XEGU: 0 0 20 0 "ms"
SVR.UPDATE_TIME.COMMENT: "update time"
SVR.UPDATE_TIME.EGU: 1 1 1e+06 0 "counts"
SVR.UPDATE_TIME.XEGU: 0 0 100 0 "ms"
SVR.USR1_TIME.COMMENT: "run time of SIGUSR1"
SVR.USR1_TIME.EGU: 1 1 1e+06 1517578888 "counts"
SVR.USR1_TIME.XEGU: 0 0 100 1517578888 "ms"
SVR.USR1_PERIOD.COMMENT: "time between SIGUSR1"
SVR.USR1_PERIOD.EGU: 1 1 1e+06 1517578888 "counts"
SVR.USR1_PERIOD.XEGU: 0 0 500 1517578888 "ms"
SVR.ARCH_GET_TIME.COMMENT: "time per archiver get"
SVR.ARCH_GET_TIME.EGU: 1 1 1e+06 1517578888 "counts"
SVR.ARCH_GET_TIME.XEGU: 0 0 100 1517578888 "ms"
SVR.LAFL: 104857600
SVR.ERROR_COUNT: 0
DEVICE.ONLINE: 1
DEVICE.OFFLINE: 1
SVR.DEVMAX: 0
SVR.TINERUN: 0
SVR.TINEVERS: "4.06.0002"
SVR.TINEPREF: ""
SVR.TINESUFF: ""
SVR.TINE_DBG: 0
SVR.TINE_LOG: 0
SVR.TINE_FEC: 0 0 0 0 ""
SVR.TINE_PORT: 0
SVR.TINE_MTU: 1472
SVR.TINE_CTSZ: 32
SVR.TINE_MCTTL: 16
SVR.TINE_BLIM: 1000
SVR.TINE_CDLY: 20
SVR.TINE_GROUP: 0
SVR.FACILITY: "TEST.DOOCS"
SVR.DEVICE: "LOCALHOST_610498009"
T_ZERO: 700
SVR.BPN: 0
SVR.SPR: 0
}
eq_fct_name: "MYTEST"
eq_fct_type: 500
{
NAME: "MYTEST"
}

Example make file:

#
# File: Makefile
#
# server/examples/clean_template
#
DOOCSROOT = ../../..
# to define DOOCSROOT as an absolute path
include $(DOOCSROOT)/$(DOOCSARCH)/DEFINEDOOCSROOT
# to define the arch dependend things
include $(DOOCSROOT)/$(DOOCSARCH)/CONFIG

SERVER_NAME = clean_template_server
OBJDIR = $(DOOCSROOT)/$(DOOCSARCH)/obj/server/example/$(PKGDIR)
SRCDIR = $(DOOCSROOT)/server/example/$(PKGDIR)
DOCDIR = /web/tesla/doocs/server/$(PKGDIR)

SOURCEOBJ = \
 $(OBJDIR)/EqFctDummy.o \
 $(OBJDIR)/EqFctDummy2.o \
 $(OBJDIR)/clean_template_rpc_server.o

SOURCEHFILES = \
 $(SRCDIR)/EqFctDummy.h\
 $(SRCDIR)/EqFctDummy2.h

ALLPROGS = \
 $(OBJDIR)/$(SERVER_NAME)

# Custom: Server directory where the binary should be placed when making copy-server
SERVER_DIR = /export/doocs/server/$(SERVER_NAME)
SERVER_SCP_TARGET = doocsadm@xfelml2:$(SERVER_DIR)/$(SERVER_NAME)

# Custom: Activate C++14 support
# CCFLAGS += -std=c++0x

# Colorful separators
INTRO = "\033[1;34m------------"
OUTRO = "------------\033[0m"

all: $(ALLPROGS)

$(OBJDIR)/.depend depend:
	@echo $(INTRO) $@ $(OUTRO)
	@if [ ! -f $(OBJDIR) ] ; then \
		echo ---------- create dir $(OBJDIR) --------------; \
		mkdir -p $(OBJDIR) ; \
	fi
	for i in $(SRCDIR)/*.cc ;do $(CCDEP) $$i ;done >> $(OBJDIR)/.depend_temp
	cat $(OBJDIR)/.depend_temp | sed -e "/:/s/^/\$$\(OBJDIR\)\//g" > $(OBJDIR)/.depend
	chmod g+w $(OBJDIR)/.depend*

include $(OBJDIR)/.depend

$(OBJDIR)/$(SERVER_NAME): $(SOURCEOBJ)
	@echo $(INTRO) $@ $(OUTRO)
	$(LINK.cc) $(LDFLAGS) -o $(OBJDIR)/$(SERVER_NAME) $(SOURCEOBJ) \
 		-lEqServer -lDOOCSapi -lIOBUS $(LDLIBS)
	@chmod g+w $(OBJDIR)/$(SERVER_NAME)

static $(OBJDIR)/static_$(SERVER_NAME): $(SOURCEOBJ)
	@echo $(INTRO) $@ $(OUTRO)
	$(LINK.cc.static) $(LDFLAGS) -o $(OBJDIR)/static_$(SERVER_NAME) $(SOURCEOBJ) \
		 -lEqServer -lDOOCSapi -lIOBUS \
		 $(LDLIBS)
	@chmod g+w $(OBJDIR)/static_$(SERVER_NAME)

.PHONY: clean
clean:

	@echo $(INTRO) $@ $(OUTRO)
	rm -f $(SOURCEOBJ) $(OBJDIR)/*.o $(SRCDIR)/*.ps $(OBJDIR)/$(SERVER_NAME) $
(OBJDIR)/.depend*

# Custom: copy the server binary directly onto the server (instead of building and installing a full package)
.PHONY: copy-server
copy-server: $(ALLPROGS)
	@echo $(INTRO) $@ $(OUTRO)
	chgrp -R doocscvs $(OBJDIR)
	chmod g+x $(OBJDIR)
	chmod -R g+r $(OBJDIR)
	scp $(ALLPROGS) $(SERVER_SCP_TARGET)

.PHONY: doc
doc: doxygen
	@echo Done.

.PHONY: doxygen
doxygen:
	@echo $(INTRO) $@ $(OUTRO)
	-doxygen
	-chmod -R a+r $(DOCDIR)
	-chmod -R ug+w $(DOCDIR)
	-find $(DOCDIR) -type d -exec chmod a+x {} ';'

.PHONY: package
package: $(ALLPROGS)
	@echo $(INTRO) $@ $(OUTRO)
	create_package
  • No labels