Bienvenue dans PMDAQ
Cette documenation décrit l'installation et l'utilisation de pmdaq à Lyon.
Installation
L'installation par défaut est prévue pour se faire sur une version récente de linux sous Ubuntu (>20.X). L'installation sous une autre version de linux ou de macOs est possible mais n'est pas maintenue.
Pmdaq est basée sur deux librairies:
- cpprest le framework C++ de Microsoft pour l'échange de message REST (message en JSON vers des services web). Elle est utilisée pour le contrôle et la configuration des processus
- ZeroMQ une librairie d'échange de messages binaires utilisée pour la collecte des données
D'autres librairies sont également installées mais sont principalement liées au hardware utilisé à Lyon, les principales sont:
- libftdi pour la lecture des cartes d'acquisition utilsant le bus USB
- netlink++ pour la lecture de celle utilisant des sockets TCP/IP via une chip Wiznet
- paho mqtt la librairie d'interface MQTT en C++ et python fournit par Eclipse.
Nouvelle installation
Afin de faciliter l'installation des différents software un script pmdaq_installer.sh est fourni et doit être recopié dans /usr/local/bin:
wget https://mirabitl.web.cern.ch/daq_install/pmdaq_installer.sh
chmod +x pmdaq_installer.sh
sudo cp pmdaq_installer.sh /usr/local/bin
Et les commandes dispobibles sont obtenues avec:
mirabito@lyosdhcal15:~$ pmdaq_installer.sh -h
Usage :
-D : Update and install needed debian packages
-M : Update and install needed brew packages on apple MACOS
-C : Update and compile pmdaq
-p : Build the distribution packages.
-h : print this message.
BUILDDIR is set
Build is in /home/mirabito/Build /home/mirabito/Build
number of processor 4
Comme on peut le voir sur cet exemple une variable d'environnement BUILDIR doit être déclarée pour spécifier à quel endroit la compilation doit s'effectuer:
mkdir -p ${HOME}/build
export BUILDIR=${HOME}/build
Installation des paquets sous Ubuntu
pmdaq_installer.sh -D
Cette commande installera les paquets debian nécessaires, télechargera les librairies (netlink,paho,0mq.. ) devant être compilées et les installera.
Compilation et installation du code
pmdaq_installer.sh -C
Cette commande:
- Crée le répertoire /opt/pmdaq s'il n'existe pas et télécharge le répertoire git de pmdaq
- Met à jour le répertoire /opt/pmdaq et lance les compilations nécessaires avec cmake
Et l'on obtient l'installation suivante dans /usr/local/pmdaq
/usr/local/pmdaq/
├── bin
│ ├── combdaq
│ ├── daq_webaccess
│ ├── daq_webaccess.service
│ ├── daq_webaccess_service_daemon
│ ├── dbt
│ ├── mga
│ ├── mgjob
│ ├── mgmqtt
│ ├── mgroc
│ ├── mgslow
│ ├── mg_webaccess
│ ├── mg_webaccess.service
│ ├── mg_webaccess_service_daemon
│ ├── pmd
│ ├── pmdaemon
│ ├── pmdaq_service_daemon
│ ├── pnsdaemon
│ ├── pnse
│ ├── pns_service_daemon
│ ├── scannet
│ └── slowctrl
├── etc
│ ├── Log4cxxConfig.xml
│ ├── pmdaq.bashrc
│ ├── pmdaq.conf
│ ├── pmdaq.service
│ ├── pns.conf
│ └── pns.service
├── lib
│ ├── libapp_demo.so
│ ├── libapp_genesys.so
│ ├── libapp_syx27.so
│ ├── libapp_wiener.so
│ ├── libapp_zup.so
│ ├── libevb_builder.so
│ ├── libevb_producer.so
│ ├── libevb.so
│ ├── libgrStore.so
│ ├── liblyon_db.so
│ ├── liblyon_dif.so
│ ├── liblyon_febv1.so
│ ├── liblyon_febv2.so
│ ├── liblyon_gricv0.so
│ ├── liblyon_gricv1.so
│ ├── liblyon_ipdc.so
│ ├── liblyon_liboard.so
│ ├── liblyon_mbdaq0.so
│ ├── liblyon_mbmdcc.so
│ ├── liblyon_mdcc.so
│ ├── liblyon_mpi.so
│ ├── liblyon_pmr.so
│ ├── liblyon_ptdc.so
│ ├── liblyon_sdcc.so
│ ├── liblyon_shm_data_source.so
│ ├── liblyon_wbmdcc.so
│ ├── liblyon_wizcc.so
│ ├── liblyon_wtricv0.so
│ ├── liblyon_wtricv1.so
│ ├── libmgStore.so
│ ├── libmqttStore.so
│ ├── libpmdaq.so
│ ├── libpns.so
│ ├── libproc_binarywriter.so
│ ├── libproc_dummywriter.so
│ └── libproc_shmwriter.so
└── share
├── combrc.py
├── combrc_threaded.py
├── daqrc.py
├── mbmdcc.py
├── MongoAsic.py
├── MongoHR2.py
├── MongoJob.py
├── MongoLiroc.py
├── MongoMqtt.py
├── MongoMROC.py
├── MongoPR2.py
├── MongoRoc.py
├── MongoSlow.py
├── pmdaqrc.py
├── pnsAccess.py
├── prettyjson.py
├── rcslow.py
├── serviceAccess.py
├── session.py
└── xml2mongo.py
Configuration
Pour compléter l'installation il faut installer les 2 services linux pmdaq et pns :
- pmdaq C'est le webserver gérant les applications d'acquisition, la configuration d'éxecution se trouve dans /usr/local/pmdaq/etc/pmdaq.conf
# Mongo DB account (if used)
export MGDBLOGIN=aXXXXc/RXXXX8@lyoxxxx:27017@LYONROC
# process Name server
export PNS_NAME=lyoXXXX.in2p3.fr
# Host and port
export PMDAQ_NAME=lyoXXXX.in2p3.fr
export PMDAQ_PORT=7777
- Il faut modifier le nom de la machine et le chemin d'accès à la base de donnée
- Il faut ensuite copier la configuration dans /etc et démarrer le service:
sudo cp /usr/local/pmdaq/etc/pmdaq.conf /etc
sudo cp /usr/local/pmdaq/etc/pmdaq.service /lib/systemd/system/
sudo systemctl enable pmdaq
sudo service pmdaq start
- pns C'est le webserver gérant les noms et les statuts des sessions et des applications. La configuration d'éxecution se trouve dans /usr/local/pmdaq/etc/pns.conf. Une seule instance de pns est nécessaire pour un système d'acquisition donné pouvant gérer autant de service pmdaq que désiré.
# Mongo DB account (if used)
export MGDBLOGIN=aXXXXc/RXXXX8@lyoxxxx:27017@LYONROC
# PNS name
export PNS_NAME=lyoXXXX.in2p3.fr
#daemon host and port (8888 for PNS)
export PMDAQ_NAME=lyoXXXX.in2p3.fr
export PMDAQ_PORT=8888
- Il faut modifier le nom de la machine et le chemin d'accès à la base de donnée
- Il faut ensuite copier la configuration dans /etc et démarrer le service:
sudo cp /usr/local/pmdaq/etc/pns.conf /etc
sudo cp /usr/local/pmdaq/etc/pns.service /lib/systemd/system/
sudo systemctl enable pns
sudo service pns start
Docker
docker est un système de container permettant d'exécuter des taches sur une machine avec un environnement dédié sans créer de machine virtuelle ni installer de librariries particulières. Chaque service est fournit avec son environnment et utilise les ressources (disques, réseau) de la machine.
Il est nécessaire d'installer docker et docker-compose
Plusieurs services peuvent être utilisés dans notre acquisition:
- mongodb La base de donnée stockant les configurations d'acquisition et celles du hardware utilisé
- mosquitto Un service libre de MQTT utilisé par les application de slow control
- graphite Un service de visualisation du slow control avec un suivi en temps (nécessaire pour grafana)
- grafana Un autre service de visualisation du slow control avec un suivi en temps plus facilement configurable
Nous avons utilisé docker-compose pour gérer ces containers.
Configuration avec docker-compose
Pour chacun des services il faut creer une zone persistante dans /data par exemple:
docker-grafana
docker-mosquitto
graphite
mongodb
Si nécessaire le pluggin mqtt_datasource doit être compilé et installé dans:
/data/docker-grafana/grafana_plugins/mqtt-datasource
docker-compose.yml
C'est le fichier de controle de docker
version: “3.6” services:
graphite:
image : graphiteapp/graphite-statsd
container_name: graphite
environment:
- PUID=1000
- PGID=1000
volumes:
- /data/graphite/storage:/opt/graphite/storage
ports:
- 80:80
- 2003-2004:2003-2004
- 2023-2024:2023-2024
- 8126:8126
- 8125:8125/udp
restart: unless-stopped
mongodb:
image : mongo:3.6.3
container_name: mongodb
environment:
- PUID=1000
- PGID=1000
volumes:
- /data/mongodb/database:/data/db
ports:
- 27017-27019:27017-27019
restart: unless-stopped
mosquitto:
image : eclipse-mosquitto
container_name: mosquitto
environment:
- PUID=1000
- PGID=1000
volumes:
- /data/docker-mosquitto/mosquitto:/mosquitto
- /data/docker-mosquitto/mosquitto/data:/mosquitto/data
- /data/docker-mosquitto/mosquitto/log:/mosquitto/log
ports:
- 1883:1883
restart: unless-stopped
grafana:
image: grafana/grafana:8.4.4
container_name: grafana-server
restart: unless-stopped
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=Monpasswd_admin
- GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS=grafana-mqtt-datasource
ports:
- 3000:3000
volumes:
- /data/docker-grafana/grafana_plugins:/var/lib/grafana/plugins
- /data/docker-grafana/grafana_data:/var/lib/grafana
Controle
On démarre ou arrête les containers définis précèdemment avec:
docker-compose up/down
dans le répertoire contenant docker-compose.yml
Un exemple de containers docker s'éxecutant:`
acqilc@lyocms09:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e6c4fabd5d05 grafana/grafana:8.4.4 "/run.sh" 17 months ago Up 7 days 0.0.0.0:3000->3000/tcp, [::]:3000->3000/tcp grafana-server
de04ec2a0e15 graphiteapp/graphite-statsd "/entrypoint" 17 months ago Up 7 days 0.0.0.0:80->80/tcp, [::]:80->80/tcp, 0.0.0.0:2003-2004->2003-2004/tcp, [::]:2003-2004->2003-2004/tcp, 2013-2014/tcp, 8080/tcp, 0.0.0.0:2023-2024->2023-2024/tcp, [::]:2023-2024->2023-2024/tcp, 0.0.0.0:8126->8126/tcp, [::]:8126->8126/tcp, 8125/tcp, 0.0.0.0:8125->8125/udp, [::]:8125->8125/udp graphite
94bca796bb58 eclipse-mosquitto "/docker-entrypoint.…" 17 months ago Up 7 days 0.0.0.0:1883->1883/tcp, [::]:1883->1883/tcp mosquitto
560011543a60 mongo:3.6.3 "docker-entrypoint.s…" 17 months ago Up 7 days 0.0.0.0:27017-27019->27017-27019/tcp, [::]:27017-27019->27017-27019/tcp mongodb
Introduction à pmdaq
Introduction
Nous avons développé un système d'acquisition de données basé sur une API REST. Il utilise la bibliothèque cpprest [2] de Microsoft pour échanger des messages et le framework ZeroMQ [3] pour le transfert de données binaires. Offrant la collecte de données binaires, la construction d'événements, une machine à états finis accessible via le web et le contrôle des processus, il est particulièrement adapté à la gestion des sources de données de laboratoires ou de tests en faisceau. Il fournit un constructeur d'événements distribué et évolutif.
Fonctionnalités
PMDAQ est un framework d'acquisition où ZeroMQ est utilisé pour échanger des buffers binaires structurés et où les services web cpprest sont utilisés pour contrôler et configurer les processus. Il offre plusieurs fonctionnalités couramment utilisées dans l'acquisition de données moderne :
-
Contrôle des processus : Un démon PMDAQ s'exécute sur chaque ordinateur et lance un serveur web. Chaque application d'acquisition de données (DAQ) est chargée en tant que service avec un chemin spécifique et publie son état à une application spéciale PMDAQ, le PNS (Process Name Server).
-
Contrôle des applications : Du point de vue du développeur, une classe générique de machine à états finis est fournie pour faciliter l'association des transitions dans l'application. Du côté utilisateur, ces transitions sont contrôlées via des messages envoyés au démon ayant chargé l'application. Des commandes autonomes supplémentaires sont également liées aux méthodes utilisateur et peuvent être contrôlées via d'autres messages pour définir des paramètres ou surveiller l'exécution.
-
Construction d'événements : Une structure de buffer prédéfinie est disponible pour stocker tout type de données. Le processus de lecture matérielle peut publier ces buffers en utilisant ZeroMQ. Une classe collectrice est fournie pour rassembler les données de différents éditeurs et compléter les événements. Le traitement des événements et l'écriture des données sont réalisés à l'aide de plugins génériques sous Linux.
-
Contrôle d'exécution : Des exemples de scripts ou de code Python sont fournis pour contrôler le processus.
Toutes ces fonctionnalités sont principalement indépendantes et peuvent être utilisées séparément.
Environnement logiciel
cpprest [2] est une bibliothèque compatible avec REST. Elle fournit un serveur web auquel des services peuvent être attachés. Chaque application DAQ est un service supplémentaire.
ZeroMQ [3] est une bibliothèque de messagerie asynchrone haute performance, conçue pour une utilisation dans des applications distribuées ou concurrentes. Elle intègre l’échange de paquets binaires avec plusieurs modèles d’échange de messages : PUSH-PULL ou PUBLISH-SUBSCRIBE sont typiquement utilisés pour la collecte de données. Elle est légère, basée sur TCP/IP et peut être installée sur presque tous les systèmes d’exploitation actuellement utilisés.
Le format de fichier JSON [10] est choisi pour l’échange des paramètres et des fichiers de configuration. Il est largement utilisé et dispose de nombreuses bibliothèques dans divers langages permettant d’encoder ou de décoder des valeurs.
MongoDB [5], une base de données orientée documents, est particulièrement adaptée pour stocker des configurations JSON dans un environnement distribué. Elle est légère et disponible sur toutes les plateformes.
Enfin, la journalisation des messages est réalisée grâce à la bibliothèque Log4cxx. Celle-ci peut être intégrée à la console ou à un système de journalisation centralisé comme rsyslog, et utilisée dans des systèmes modernes d’analyse des journaux comme Logstash.
Cadre applicatif
Le cadre applicatif couvre plusieurs aspects. Premièrement, il doit permettre un accès à distance afin d’envoyer des commandes à chaque application individuellement. Deuxièmement, chaque application doit être contrôlée par une machine à états finis pour séquencer en toute sécurité les différentes étapes du traitement : initialisation, configuration, acquisition de données, arrêt... Enfin, chaque application doit utiliser un langage commun pour charger et modifier ses paramètres.
Le démon pmdaq et la classe baseServer
Le processus principal du démon crée un objet baseServer qui lance un serveur web sur un hôte spécifié (myhost) et un port donné (myport) avec quatre services de base :
-
http://myhost:myport/REGISTER : Ce service permet d’enregistrer de nouveaux services associés à un plugin de bibliothèque. La bibliothèque partagée est chargée et fournit une liste de noms de services et de fonctions associées.
-
http://myhost:myport/REMOVE : Supprime le plugin de bibliothèque spécifié ainsi que tous les noms et services associés.
-
http://myhost:myport/SERVICES : Retourne la liste complète des services de plugins disponibles.
-
http://myhost:myport/EXIT : Arrête le programme principal et quitte. Comme le démon pmdaq redémarre automatiquement, cela permet de redémarrer l’application depuis zéro (utile lorsqu’une bibliothèque de plugins est recompilée).
Dans la boucle principale de baseServer, un gestionnaire POST/GET de cpprest est mis en place :
void baseServer::handle_get_or_post(http_request message)
L’objet http_request message gère toutes les informations envoyées par le client appelant :
-
Le chemin relatif, c’est-à-dire le nom du service.
-
Les valeurs CGI, correspondant aux paramètres éventuels.
Il fournit également une méthode de réponse avec un code d’erreur et un contenu JSON qui sera retourné à l’appelant (navigateur, cURL ou script Python).
Dans la méthode handle_get_or_post, le chemin relatif est d’abord comparé aux services prédéfinis (REGISTER, REMOVE, SERVICES, EXIT...) et exécuté s’il correspond. Sinon, il est comparé aux services des plugins enregistrés, et la méthode associée est appelée si le nom correspond.
Tout plugin doit implémenter l’interface handlerPlugin.
class handlerPlugin
{
public:
virtual std::vector<std::string> getPaths(std::string query)=0;
virtual void processRequest(http_request& message)=0;
virtual void terminate()=0;
void setUrl(std::string s){_url.assign(s);}
std::string url(){return _url;}
private:
std::string _url;
};
La méthode getPaths retourne tous les chemins relatifs possibles gérés par le plugin, qui seront traités lors de l’appel à processRequest.
La méthode terminate garantit une fin correcte de l’exécution du code du plugin lorsqu’il est supprimé dans le traitement de baseServer.
Commandes et Machine à États Finis : l'objet fsmw
Toute application d’acquisition de données doit pouvoir répondre à des commandes distantes tout en suivant un flux de processus cohérent (seule ou en interaction avec d’autres applications). Par exemple, une reconfiguration des ASICs en front-end ne doit pas être effectuée pendant qu’un autre thread est en train de lire des données.
Cet objectif est atteint grâce à une machine à états finis (FSM - Finite State Machine) qui définit des états de processus et des transitions entre ces états.
La figure suivante illustre un FSM de base:
-
États définis :
- CREATED : L’application vient d’être chargée, aucun accès matériel ou configuration n’a encore été effectué.
- INITIALISED : L’accès à la base de données ou au matériel (socket, bus série, etc.) est établi.
- CONFIGURED : L’application est configurée via les paramètres de la base de données ou du matériel.
- RUNNING : Une boucle de thread est démarrée pour acquérir des données.
-
Transitions disponibles :
- Initialise → Passe de CREATED → INITIALISED
- Configure → Passe de INITIALISED → CONFIGURED ou permet de répéter la configuration (CONFIGURED → CONFIGURED).
- Start → Passe de CONFIGURED → RUNNING.
- Stop → Passe de RUNNING → CONFIGURED.
- Destroy → Passe de INITIALISED → CREATED ou de CONFIGURED → CREATED.
Pour implémenter un tel mécanisme, nous avons créé la classe suivante :
typedef std::function<void (http_request&)> CMDFunctor;
class fsmTransition
{
public:
/**
\brief Constructor
\param istate initial state name
\param fstate final state name
\param f CMDfunctor called
\details In an object of class MyObj with a methode create(http_request message), the CMDFunctor is declared with 'std::bind(&MyObj::create, this,std::placeholders_1)'
*/
fsmTransition(std::string istate,std::string fstate,CMDFunctor f) : _istate(istate),_fstate(fstate),_callback(f) {}
std::string initialState(){return _istate;} ///< Initial state name
std::string finalState(){return _fstate;} ///< Final state name
CMDFunctor callback(){return _callback;} ///< PFunctor access
private:
std::string _istate,_fstate;
CMDFunctor _callback;
};
Une fsmTransition est une paire de noms d'états (états initiaux/finaux) associée à une fonction gérant un message http_request, un CMDFunctor, qui peut intégrer des paramètres pour la transition et renvoyer l'état. Dans un cadre d'acquisition distribuée, la transition sera déclenchée par la réception d'un message http_request spécifique.
En plus de ce mécanisme de machine d'états finis, l'application doit également être capable de recevoir des commandes autonomes, par exemple pour changer un paramètre avant une transition ou pour donner accès à certains statuts ou codes d'erreur... Une fois de plus, cela se fait en associant un nom de commande à un CMDFunctor. Enfin, l'application doit gérer les paramètres. Avec toutes ces exigences, nous pouvons définir la classe fsmw.
- Elle doit hériter et implémenter l'interface handlerPlugin.
class fsmw : public handlerPlugin
{
public:
fsmw();
// handlerPlugin implementation
virtual std::vector<std::string> getPaths(std::string query);
virtual void processRequest(http_request& message);
virtual void terminate();
// To be implemented on inheritance
virtual void initialise();
virtual void end();
- Elle doit gérer la FSM dans une std::map les fsmTransition associée à un vecteur de noms d'états, et les commandes dans une std::map de CMDFunctor.
// FSMW commands and transitions managment
/**
\brief register a command
\param cmd name
\param istate Initial state
\param fstate Final state
\param f CMDFunctor
*/
void addCommand(std::string s,CMDFunctor f);
/**
\brief Register a new state
\param statename the name of the state
*/
void addState(std::string statename);
/**
\brief register a transition
\param cmd Transition name
\param istate Initial state
\param fstate Final state
\param f CMDFunctor(see fsmTransition class)
*/
void addTransition(std::string cmd,std::string istate,std::string fstate,CMDFunctor f);
/**
\brief Force the current state
\param s the state name
*/
void setState(std::string s);
std::string state();///< Current state name
void publishState();///< Unused
/**
\brief List all transitions
\return JSON list of transitions
*/
web::json::value transitionsList();
/**
\brief List all possible transitions for the current state
\return JSON list of transitions
*/
web::json::value allowList();
....
private:
std::map<std::string,CMDFunctor> _commands;
std::vector<std::string> _states;
std::string _state;
std::map<std::string,std::vector<fsmTransition> > _transitions;
- Les paramètres sont stockés dans un objet JSON et l'accès est également donné au chemin web complet (décrit ci-dessous).
web::json::value& params(){return _params;}
std::string host(){return _host;}
uint32_t port(){return _port;}
std::string path(){return _basePath;}
std::string session(){return _p_session;}
std::string name(){return _p_name;}
uint32_t instance(){return _p_instance;}
protected:
std::string _host;
uint32_t _port;
std::string _basePath;
std::string _p_session;
std::string _p_name;
uint32_t _p_instance;
web::json::value _params;
- Trois commandes par défaut sont définies :
- INFO avec le callback
void info(http_request message)
qui renvoie la liste des commandes et transitions au format JSON. - PARAMS avec le callback
void getparams(http_request message)
qui renvoie la liste des paramètres au format JSON. - SETPARAMS avec le callback
void setparams(http_request message)
, avec un paramètre CGI params contenant la chaîne JSON des paramètres que nous souhaitons mettre à jour ou créer. Il renvoie la liste des paramètres mis à jour au format JSON.
- INFO avec le callback
La méthode GetPaths
La méthode principale pour enregistrer un plugin est std::vectorstd::string getPaths(std::string query)
. Elle commence par analyser la chaîne de requête pour trouver :
- session : Le nom de la session, c'est-à-dire de l'acquisition.
- name : Le nom du plugin.
- instance : Le numéro de l'instance.
- params : Une chaîne JSON contenant tous les paramètres du plugin au format JSON.
À partir de ces chaînes, elle construit le basePath=session/name/instance/, qui préfixe tous les noms de transitions ou de commandes.
La méthode initialise est ensuite appelée. Par défaut vide, c'est à cet endroit que les classes filles déclarent leurs états, transitions et commandes, comme montré dans l'exemple suivant.
void Febv2Manager::initialise()
{
// Register states
this->addState("PREINITIALISED");
this->addState("INITIALISED");
this->addState("CONFIGURED");
this->addState("RUNNING");
// Add transitions
this->addTransition("CREATEFEB", "CREATED", "PREINITIALISED", std::bind(&Febv2Manager::createfeb, this, std::placeholders::_1));
this->addTransition("INITIALISE", "PREINITIALISED", "INITIALISED", std::bind(&Febv2Manager::fsm_initialise, this, std::placeholders::_1));
this->addTransition("CONFIGURE", "INITIALISED", "CONFIGURED", std::bind(&Febv2Manager::configure, this, std::placeholders::_1));
this->addTransition("CONFIGURE", "CONFIGURED", "CONFIGURED", std::bind(&Febv2Manager::configure, this, std::placeholders::_1));
this->addTransition("START", "CONFIGURED", "RUNNING", std::bind(&Febv2Manager::start, this, std::placeholders::_1));
this->addTransition("STOP", "RUNNING", "CONFIGURED", std::bind(&Febv2Manager::stop, this, std::placeholders::_1));
this->addTransition("DESTROY", "CONFIGURED", "PREINITIALISED", std::bind(&Febv2Manager::destroy, this, std::placeholders::_1));
this->addTransition("DESTROY", "INITIALISED", "PREINITIALISED", std::bind(&Febv2Manager::destroy, this, std::placeholders::_1));
// Add commands
this->addCommand("STATUS", std::bind(&Febv2Manager::c_status, this, std::placeholders::_1));
this->addCommand("DOWNLOADDB", std::bind(&Febv2Manager::c_downloadDB, this, std::placeholders::_1));
this->addCommand("VTHSHIFT", std::bind(&Febv2Manager::c_vthshift, this, std::placeholders::_1));
this->addCommand("PACCOMP", std::bind(&Febv2Manager::c_paccomp, this, std::placeholders::_1));
this->addCommand("DELAY_RESET", std::bind(&Febv2Manager::c_delay_reset, this, std::placeholders::_1));
}
Enfin, la méthode retourne une liste de tous les noms de commandes et de transitions, préfixée par le basePath. Ces chemins de service sont associés au callback processRequest dans le gestionnaire baseServer.
Méthode processRequest
Le callback central de tous les services enregistrés par l'objet est void processRequest(http_request& message)
.
Elle parcourt simplement la map des commandes et tente de trouver un chemin correspondant. En cas de correspondance, elle transmet la requête au CMDFunctor associé. Pour les transitions, le même mécanisme est utilisé, avec un test supplémentaire pour vérifier que l'état actuel de la FSM est compatible avec la transition requise.
Gestion des états
Chaque objet fsmw publie tout changement d'état au PNS (voir section ci dessou) défini par la variable d'environnement PNS_NAME. Lorsque le plugin est supprimé, la méthode terminate est appelée, publiant l'état DEAD au PNS et appelant la méthode virtuelle end implémentée par l'objet utilisateur fille.
Implémentation utilisateur
Finalement, l'implémentation d'une application héritant de fsmw nécessite la redéfinition de deux méthodes :
-
void initialise()
: Elle est appelée dans getPaths() et est l'endroit où la FSM, les transitions et les commandes sont déclarées. Bien sûr, l'utilisateur doit coder les callbacks de chaque transition ou commande ajoutée. -
void end()
: Elle est appelée dans la méthode terminate() avant la suppression du plugin et doit terminer proprement toute tâche en cours.
Contrôle des applications
Description de l'acquisition
La configuration d'une acquisition se fait via un fichier JSON contenant la description d'une session, identifiée par son nom, un numéro de version, un type, le PNS utilisé et, bien sûr, une liste d'applications, comme dans l'exemple suivant :
{
"session": "tel_feb","type": "daq","version": 4,"pns": "lyocms09",
"apps": [
{"host": "lyocms09.in2p3.fr","instance": 0,"name": "evb_builder",
"params": {"collectingPort": 5556,
"directory": "/data/local/TEL_CMS/",
"processor": ["proc_binarywriter"],
"purge": 0},
"port": 7777},
{"host": "lyocms09.in2p3.fr","instance": 0,"name": "lyon_mbmdcc",
"params": {"channels": 4,
"mbmdcc": {"address": "192.168.100.205",
"network": "192.168.100."},
"spilloff": 10000,"spillon": 2000000,"spillregister": 64},
"port": 7777},
....
]
}
Le parsing de cette description permet d'enregistrer chaque application sur le démon PMDAQ correspondant. La commande REGISTER suivante
curl -G --data-urlencode 'host=lyocms09.in2p3.fr' --data-urlencode 'session=tel_feb' --data-urlencode 'instance=0' --data-urlencode 'name=lyon_mbmdcc' --data-urlencode 'port=7777' --data-urlencode 'params={"channels":4,"mbmdcc": {"address": "192.168.100.205","network": "192.168.100."},"spilloff": 10000,"spillon": 2000000,"spillregister": 64}' http://lyocms09.in2p3.fr:7777/REGISTER
chargera le plugin de /usr/local/pmdaq/lib/lib_lyon_mbmdcc.so et créera tous les services associés sur le démon http://lyocms09:7777 pour la session tel_feb et l'instance 0.
Une fois le programme chargé, les commandes prédéfinies donnent:
- SERVICES
curl -g http://lyocms09:7777/SERVICES
["/tel_feb/lyon_mbmdcc/0/CALIBOFF","/tel_feb/lyon_mbmdcc/0/CALIBON","/tel_feb/lyon_mbmdcc/0/CHANNELON","/tel_feb/lyon_mbmdcc/0/CONFIGURE","/tel_feb/lyon_mbmdcc/0/DESTROY","/tel_feb/lyon_mbmdcc/0/GETREG","/tel_feb/lyon_mbmdcc/0/INFO","/tel_feb/lyon_mbmdcc/0/INITIALISE","/tel_feb/lyon_mbmdcc/0/PARAMS","/tel_feb/lyon_mbmdcc/0/PAUSE","/tel_feb/lyon_mbmdcc/0/READREG","/tel_feb/lyon_mbmdcc/0/RELOADCALIB","/tel_feb/lyon_mbmdcc/0/RESET","/tel_feb/lyon_mbmdcc/0/RESETFSM","/tel_feb/lyon_mbmdcc/0/RESETTDC","/tel_feb/lyon_mbmdcc/0/RESUME","/tel_feb/lyon_mbmdcc/0/RESYNC","/tel_feb/lyon_mbmdcc/0/SETCALIBCOUNT","/tel_feb/lyon_mbmdcc/0/SETCALIBREGISTER","/tel_feb/lyon_mbmdcc/0/SETEXTERNAL","/tel_feb/lyon_mbmdcc/0/SETHARDRESET","/tel_feb/lyon_mbmdcc/0/SETPARAMS","/tel_feb/lyon_mbmdcc/0/SETREG","/tel_feb/lyon_mbmdcc/0/SETSPILLREGISTER","/tel_feb/lyon_mbmdcc/0/SETSPSSPILL","/tel_feb/lyon_mbmdcc/0/SETTRIGEXT","/tel_feb/lyon_mbmdcc/0/SPILLOFF","/tel_feb/lyon_mbmdcc/0/SPILLON","/tel_feb/lyon_mbmdcc/0/STATUS","/tel_feb/lyon_mbmdcc/0/WRITEREG"]
- INFO
curl -g http://lyocms09:7777/tel_feb/lyon_mbmdcc/0/INFO
{"ALLOWED":["/tel_feb/lyon_mbmdcc/0/INITIALISE"],
"COMMANDS":["/tel_feb/lyon_mbmdcc/0/CALIBOFF","/tel_feb/lyon_mbmdcc/0/CALIBON","/tel_feb/lyon_mbmdcc/0/CHANNELON","/tel_feb/lyon_mbmdcc/0/GETREG","/tel_feb/lyon_mbmdcc/0/INFO","/tel_feb/lyon_mbmdcc/0/PARAMS","/tel_feb/lyon_mbmdcc/0/PAUSE","/tel_feb/lyon_mbmdcc/0/READREG","/tel_feb/lyon_mbmdcc/0/RELOADCALIB","/tel_feb/lyon_mbmdcc/0/RESET","/tel_feb/lyon_mbmdcc/0/RESETFSM","/tel_feb/lyon_mbmdcc/0/RESETTDC","/tel_feb/lyon_mbmdcc/0/RESUME","/tel_feb/lyon_mbmdcc/0/RESYNC","/tel_feb/lyon_mbmdcc/0/SETCALIBCOUNT","/tel_feb/lyon_mbmdcc/0/SETCALIBREGISTER","/tel_feb/lyon_mbmdcc/0/SETEXTERNAL","/tel_feb/lyon_mbmdcc/0/SETHARDRESET","/tel_feb/lyon_mbmdcc/0/SETPARAMS","/tel_feb/lyon_mbmdcc/0/SETREG","/tel_feb/lyon_mbmdcc/0/SETSPILLREGISTER","/tel_feb/lyon_mbmdcc/0/SETSPSSPILL","/tel_feb/lyon_mbmdcc/0/SETTRIGEXT","/tel_feb/lyon_mbmdcc/0/SPILLOFF","/tel_feb/lyon_mbmdcc/0/SPILLON","/tel_feb/lyon_mbmdcc/0/STATUS","/tel_feb/lyon_mbmdcc/0/WRITEREG"],
"STATE":"CREATED",
"TRANSITIONS":["/tel_feb/lyon_mbmdcc/0/CONFIGURE","/tel_feb/lyon_mbmdcc/0/DESTROY","/tel_feb/lyon_mbmdcc/0/INITIALISE"]}
- PARAMS
curl -g http://lyocms09:7777/tel_feb/lyon_mbmdcc/0/PARAMS
{"channels":4,"mbmdcc":{"address":"192.168.100.205","network":"192.168.100."},"path":"/tel_feb/lyon_mbmdcc/0/","spilloff":10000,"spillon":2000000,"spillregister":64}
Serveur de Noms de Processus (PNS)
À ce stade, nous avons déjà un cadre qui nous permet de construire un système d'acquisition distribué complet. L'application peut être exécutée sur n'importe quel ordinateur avec un démon PMDAQ, et un script (bash ou python) peut déclencher n'importe quelle transition structurée avec un simple parsing de fichier. Cependant, un tel script devra interroger tous les ordinateurs et services pour connaître le statut complet de l'acquisition.
La solution consiste à centraliser l'état de toutes les applications en cours d'exécution et potentiellement un état global de chaque session. Cela aurait pu être fait en utilisant une base de données, chaque processus publiant tout changement d'état vers celle-ci, mais nous avons choisi de créer un plugin dédié, le Process Name Server (PNS). C'est un plugin PMDAQ avec les capacités suivantes :
Pour les applications :
Il conserve un vecteur de paires de chaînes de caractères
-
/PNS/CLEAR : Efface le vecteur des plugins et le vecteur des sessions.
-
/PNS/LIST : Retourne la liste des applications REGISTERED avec leur état.
curl -g http://lyocms09:8888/PNS/LIST
{"REGISTERED":["lyocms09.in2p3.fr:7777:/tel_feb/evb_builder/0/?CREATED","lyocms09.in2p3.fr:7777:/tel_feb/lyon_febv2/0/?CREATED","lyocms09.in2p3.fr:7777:/tel_feb/lyon_mbmdcc/0/?CREATED","lyocms09.in2p3.fr:7777:/tel_feb/lyon_pmr/0/?CREATED"]}
- /PNS/UPDATE : Met à jour ou crée l'état d'un plugin d'application. Il nécessite des valuers pour les paramètres host, port, path et state. Par exemple, nous pouvons déclarer l'event builder comme DEAD :
curl -G --data-urlencode 'host=lyocms09.in2p3.fr' --data-urlencode 'port=7777' --data-urlencode 'path=/tel_feb/evb_builder/0/' --data-urlencode 'state=DEAD' http://lyocms09:8888/PNS/UPDATE
{\"REGISTERED\":[\"lyocms09.in2p3.fr:7777:/tel_feb/evb_builder/0/?DEAD\",\"lyocms09.in2p3.fr:7777:/tel_feb/lyon_febv2/0/?CREATED\",\"lyocms09.in2p3.fr:7777:/tel_feb/lyon_mbmdcc/0/?CREATED\",\"lyocms09.in2p3.fr:7777:/tel_feb/lyon_pmr/0/?CREATED\"]}
- /PNS/PURGE : Supprime tout plugin DEAD de la liste. Dans l'exemple précédent, l'event builder disparaîtra de la liste.
curl -G -g http://lyocms09:8888/PNS/PURGE
{\"REGISTERED\":[\"lyocms09.in2p3.fr:7777:/tel_feb/lyon_febv2/0/?CREATED\",\"lyocms09.in2p3.fr:7777:/tel_feb/lyon_mbmdcc/0/?CREATED\",\"lyocms09.in2p3.fr:7777:/tel_feb/lyon_pmr/0/?CREATED\"]}
- /PNS/REMOVE : Supprime le plugin spécifié (host, port, path).
curl -G --data-urlencode 'host=lyocms09.in2p3.fr' --data-urlencode 'port=7777' --data-urlencode 'path=/tel_feb/lyon_mbmdcc/0/' http://lyocms09:8888/PNS/REMOVE
{\"REGISTERED\":[\"lyocms09.in2p3.fr:7777:/tel_feb/lyon_febv2/0/?CREATED\",\"lyocms09.in2p3.fr:7777:/tel_feb/lyon_pmr/0/?CREATED\"]}
Pour les Sessions :
Il conserve un vecteur de paires de chaînes
-/PNS/SESSION/LIST : Liste toutes les sessions et leur état.
curl -g http://lyocms09:8888/PNS/SESSION/LIST
{"REGISTERED":["tel_feb:CREATED"]}
- /PNS/SESSION/UPDATE met à jour l'état d'une session
curl -G --data-urlencode 'session=tel_feb' --data-urlencode 'state=DEAD' http://lyocms09:8888/PNS/SESSION/UPDATE
{"REGISTERED":["tel_feb:DEAD"]}
- /PNS/SESSION/PURGE : Supprime les sessions DEAD de la liste.
curl -g http://lyocms09:8888/PNS/SESSION/PURGE
{"REGISTERED":null}
Dans une application fsmw, si l’hôte du PNS est connu ($PNS_NAME), l’application met à jour son état dans le PNS après chaque transition.
Les autres services sont utilisés par des scripts de contrôle d’exécution (Python ou JavaScript), qui seront décrits plus tard dans cette documentation.
Base de donnée MongoDB
Pour stocker et accéder à distance au fichier de configuration, on peut le placer sur un emplacement réseau partagé comme un répertoire NFS. Une autre solution consiste à l’héberger sur une page web publique et à y accéder via son URL.
Nous avons également développé un stockage basé sur une base de données orientée documents utilisant MongoDB [5]. Chaque fichier est stocké dans la collection configurations, où chaque élément possède les attributs suivants :
- content : le fichier de configuration
- name : le nom de la configuration
- version : un numéro de version
- time : l’horodatage d’insertion en epoch
- comment : un commentaire utilisateur
L’emplacement de la base de données est spécifié par la variable d’environnement MGDBLOGIN :
export MGDBLOGIN=user/pwd@pcdbname:mongoport@DBNAME
L’accès à distance est possible via des tunnels SSH. Un objet MongoJob en Python est fourni pour faciliter l’insertion et la récupération de fichiers, ainsi que des fonctionnalités supplémentaires pour le contrôle des exécutions et la gestion des runs.
Une description détaillée de la configuration de la base de données et de l'utilisation de MongoJob est donnée dans cette partie.
Configuration de la base
Serveur: Création des utilisateurs
Pour la base MaBase
use MaBase
db.createUser({
user: 'monId',
pwd: 'secretPassword',
roles: [{ role: 'readWrite', db:'MaBase'}]
})
Puis on modifie /etc/mongod.conf
# network interfaces
net:
port: 27017
bindIp: 0.0.0.0
security:
authorization: 'enabled'
On peut ainsi accéder a la base MaBase à distance
mongo -u monId -p secretPassword MONPC/MaBase
Création de la base
La création des table dans la base de donnees se fait par insertion d'objets dans les tables
Pour creer la base elle meme, il suffit de se connecter avec mongo ou mongosh et de taper:
use MaBase
pour creer la base MaBase
Les tables sont créées par insertion par les scripts python
Sauvegarde et restauration
Pour sauvegarder une base sur un PC distant, on utilise le script mongodump:
mongodump --forceTableScan --host monPC --port 27017 --username monId --password monPwd --db MaBase --out /data/local/mgbackup17/
Pour restaurer sur un autre monPC1 , le script mongorestore est le pendant de mongodump:
Installer mongodb sur monPC1
creer l'utilisateur monID
copier le repertoire mgbackup17
Et ensuite
mongorestore mgbackup17/
recrée la base sur monPC1.
MongoDB fournit également des outils pour distribuer le données sur plusieurs machines miroirs et garantir ainsi un accès continu. Enfin il est également possible de demander un compte dans un des centres de calculs (CCIN2P3 ou CERN)
Contrôle d'exécution
Le contrôle d'exécution repose sur deux aspects :
- Il organise l'exécution cohérente des différentes applications (initialisation, configuration, exécution, gestion des enregistrements, etc.).
- Il fournit une interface permettant à l’utilisateur d’interagir avec les applications.
Dans cette section, nous présenterons trois manières d'implémenter ces fonctionnalités :
- Utilisation de CURL en ligne de commande (Bash)
- Scripts en Python
- Pages web dynamiques avec JavaScript
Approche avec CURL
CURL est une interface en ligne de commande permettant de transférer des données via des URL.
Il s’adapte parfaitement à notre besoin de communication avec les services REST de PMDAQ.
Nous avons déjà montré dans les sections précédentes que CURL permet d’interagir facilement avec tous les services du daemon (enregistrement, suppression, interaction avec les plugins, etc.).
Cependant, la communication dans PMDAQ repose sur des objets JSON, ce qui nécessite un bon outil d'analyse pour automatiser et structurer le contrôle.
On peut utiliser des outils d'analyse en ligne de commande comme JQ.
Par exemple, en analysant un fichier de configuration, on peut générer des ensembles de commandes comme celui-ci :
cat /dev/shm/mgjob/tel_feb_4.json | jq '.apps[] | "curl -G --data-urlencode ''params=\(.params)'' http://\(.host):\(.port)/toto/\(.name)/\(.instance)/SETPARAMS"'
"curl -G --data-urlencode params={\"collectingPort\":5556,\"directory\":\"/data/local/TEL_CMS/\",\"processor\":[\"proc_binarywriter\"],\"purge\":0} http://lyocms09.in2p3.fr:7777/toto/evb_builder/0/SETPARAMS"
"curl -G --data-urlencode params={\"channels\":4,\"mbmdcc\":{\"address\":\"192.168.100.205\",\"network\":\"192.168.100.\"},\"spilloff\":10000,\"spillon\":2000000,\"spillregister\":64} http://lyocms09.in2p3.fr:7777/toto/lyon_mbmdcc/0/SETPARAMS"
Mais le scriptage de séquences d'acquisition complexes peut s'avérer difficile.
Nous avons donc conservé l'approche CURL principalement pour le débogage et la surveillance.
curl -g http://lyocms09.in2p3.fr:7777/tel_feb/evb_builder/0/STATUS | jq -r '.answer | "Builder Event number \(.event)" '
Builder Event number 0
Script Python
L'utilisation de Python permet de créer des objets gérant à la fois la configuration et tous les accès nécessaires aux applications d'acquisition (DAQ). Nous avons défini plusieurs niveaux d'objets :
serviceAccess : Gestion d'une application
C'est le niveau le plus bas pour le contrôle d'une application. Il utilise les bibliothèques urllib, urllib2 et socket (avec support SOCKS).
Deux méthodes principales sont définies :
- executeRequest(url)
: Effectue une requête HTTP.
- executeCMD(host, port, path, params)
: Construit une URL de la forme http://host:port/path
et ajoute les paramètres CGI donnés sous forme de dictionnaire.
Ces méthodes permettent toute interaction avec notre système DAQ.
L’objet serviceAccess est construit avec les informations suivantes : host, port, session, name, instance
Il propose ensuite les méthodes :
create()
: Enregistre le plugin (REGISTER).remove()
: Supprime le plugin (REMOVE).service_request()
: Envoie les commandes INFO et PARAMS pour récupérer et stocker les commandes, transitions et paramètres du plugin.sendCommand(name, content)
: Envoie la commandename
aveccontent
en paramètre si elle est disponible.sendTransition(name, content)
: Envoie la transitionname
aveccontent
en paramètre si elle est autorisée.
sessionAccess : Gestion d'une session d'acquisition
Une fonction permet d'instancier un objet sessionAccess à partir du fichier de description JSON:
create_session(configFileName)
:
- Analyse le fichier JSON de configuration.
- Crée un objet
serviceAccess
pour chaque application déclarée. - Enregistre les plugins si nécessaire.
- Retourne un objet
sessionAccess
.
L’objet sessionAccess
gère l’ensemble des serviceAccess
déclarés dans la configuration et synchronise l’état de la session via le PNS :
- pns_list(req_session="NONE")
: Retourne la liste des plugins et leur état.
- pns_session_list(req_session="NONE")
: Retourne les paires session/état.
- pns_session_update(new_state)
: Met à jour ou crée l’état d’une session dans la liste PNS.
- remove(obj_name=None)
:
- Supprime un serviceAccess spécifique ou tous les services.
- Effectue un PURGE des plugins et de la session elle-même.
- commands(cmd, obj_name, params=None)
:
- Envoie la commande cmd
à tous les plugins portant le nom obj_name
, quel que soit l’hôte.
- Utile pour distribuer une commande sur plusieurs machines (ex. lecture de matériel en Front-end).
- transitions(cmd, obj_name, params=None)
:
- Envoie la transition cmd
à tous les plugins portant le nom obj_name
, quel que soit l’hôte.
daqControl : Interface de contrôle haut niveau
Cett interface de run control est définie dans le fichier daqrc.py
. Elle peut servir de classe de base pour des implémentations plus complexes.
Elle gère un objet sessionAccess et une FSM globale basée sur le package python TRANSITIONS.
Son constructeur donne un aperçu de son utilisation.
def __init__(self, config_file):
self.config_file = config_file
# parse config file
#print(self.config_file)
self.session = session.create_session(config_file)
# DAQ PART
self.daq_answer = None
self.daqfsm = Machine(model=self, states=[
'CREATED', 'INITIALISED', 'CONFIGURED', 'RUNNING', 'CONFIGURED'], initial='CREATED')
self.daqfsm.add_transition(
'initialise', 'CREATED', 'INITIALISED', after='daq_initialising', conditions='isConfigured')
self.daqfsm.add_transition('configure', [
'INITIALISED', 'CONFIGURED'], 'CONFIGURED', after='daq_configuring', conditions='isConfigured')
self.daqfsm.add_transition(
'start', 'CONFIGURED', 'RUNNING', after='daq_starting', conditions='isConfigured')
self.daqfsm.add_transition(
'stop', 'RUNNING', 'CONFIGURED', after='daq_stopping', conditions='isConfigured')
self.daqfsm.add_transition(
'destroy', 'CONFIGURED', 'CREATED', after='daq_destroying', conditions='isConfigured')
Définition de la machine d'état (Machine
)
Dans l’objet Machine
, nous définissons les états et ajoutons des transitions :
États principaux : CREATED, INITIALISED, CONFIGURED, RUNNING
Chaque transition est définie avec : - Une liste d’états initiaux. - L’état final. - Le nom du callback exécuté après la transition.
Effet de Model=self
L’attribut Model=self
dans la configuration de la machine d’état a deux effets :
1. Définition automatique des méthodes :
- Les méthodes initialise
, configure
, start
, stop
, destroy
sont créées dans l’objet daqControl
.
- Lorsqu’une de ces méthodes est appelée, elle met à jour l’état et appelle la fonction correspondante :
- daq_initialising()
- daq_configuring()
- daq_starting()
- daq_stopping()
- daq_destroying()
- Obligation de redéfinir les méthodes :
- Ces cinq méthodes (
daq_initialising
,daq_configuring
, etc.) doivent être déclarées et implémentées dans la classe qui hérite dedaqControl
.
Extension et implémentation
Cette FSM globale représente le jeu minimal d’états et de transitions utilisé à Lyon.
Une implémentation complète pour la gestion de l’acquisition SDHCAL Calice et CMS iRPC est disponible dans les fichiers :
- pmdaqrc.py
- combrc_threaded.py
Le code source se trouve dans le répertoire /opt/pmdaq/scripts/
Accès Web au Run Control
Utilisation de SPYNE
JavaScript fournit un large ensemble d'outils pour récupérer des URL ou analyser du JSON, ce qui permet d'accéder facilement à toute application pmdaq. Un problème réside cependant dans l'accès à la base de données, qui ne peut pas être effectué directement depuis les pages. Un autre inconvénient est l'absence de bibliothèque FSM en JavaScript. Nous avons donc choisi d’avoir, côté serveur, l’API Python que nous avons développée, à la fois pour l’accès à MongoDB et pour l’interface de Run Control.
Nous avons réalisé cela en utilisant SPYNE, un outil RPC (Remote Procedure Call) en Python, en mettant en place une interface avec nos scripts Python de haut niveau.
MongoDB avec mg_webacces
L’installation de MongoDB comprend des collections permettant de stocker les configurations d’acquisition, les paramètres des cartes électroniques Front-End, ainsi qu’un suivi des acquisitions enregistrées. Les classes MongoJob et MongoRoc décrite plus loin permettent une interaction complète avec la base, mais seulement quelques méthodes sont nécessaires pour interagir avec le Run Control.
SPYNE démarre un serveur web permettant de gérer des méthodes RPC. Pour l'accès à MongoDB, le script mg_webaccess
démarre un serveur sur le port 29029, hébergé sur la machine définie par la variable $SERVER_NAME
. Il possède les méthodes suivantes :
- CONFIGURATIONS() : liste toutes les configurations stockées dans la base de données.
Cette méthode est accessible via la commande suivante :
```bash curl -g http://lyoilc07.in2p3.fr:29029/CONFIGURATIONS {"CONFIGURATIONSResponse": {"CONFIGURATIONSResult": ["{\"content\": [[\"FEBPROTO_CERN_3B\", 1, \"La version qui tourne au cern en ce moment\"], [\"FEBPROTO_CERN_3B\", 2, \"Pedestal alignes et seuil a 1000 sur les asics non utilises\\n State FEB2B_1415_25\"], ....}"]}}}
Pour une commande RPC donnée, la réponse est un objet structuré de la manière suivante :
{
"RPCNameResponse": {
"RPCNameResult": ["chaîne JSON avec le contenu"]
}
}
Dans ce cas, content est un tableau de triplets [ Nom de la configuration, version, commentaire ]
.
- CONFIGURATION(name,version) : Télécharge la configuration spécifiée :
curl -g http://lyoilc07:29029/CONFIGURATION?name=test_dif\&version=4
{"CONFIGURATIONResponse": {"CONFIGURATIONResult": ["{\"content\": {\"apps\": [{\"host\": \"lyosdhcal11.in2p3.fr\", \"instance\": 0, \"name\": \"evb_builder\", \"params\": {\"collectingPort\": 5556, \"directory\": \"/data/local/MRPC/\", \"processor\": [\"proc_binarywriter\"], \"purge\": 0}, \"port\": 7777}, {\"host\": \"lyosdhcal11.in2p3.fr\", \"instance\": 0, \"name\": \"lyon_ipdc\", \"params\": {\"device\": \"DCCCCC01\", \"spilloff\": 10000, \"spillon\": 2000000, \"spillregister\": 64}, \"port\": 7777}, {\"host\": \"lyosdhcal11.in2p3.fr\", \"instance\": 0, \"name\": \"lyon_pmr\", \"params\": {\"dif\": {\"db\": {\"mode\": \"mongo\", \"state\": \"TEST_DIF\", \"version\": 1}, \"geom\": [[8, 0]]}}, \"port\": 7777}], \"pns\": \"lyosdhcal11.in2p3.fr\", \"session\": \"test_dif\", \"type\": \"daq\", \"version\": 4}}"]}}
Comme on peut le voir, les paramètres de la méthode dans SPYNE correspondent aux paramètres CGI dans l'URL.
-
STATES() : Liste toutes les configurations des Front-end stockées dans la base de données.
-
DOWNLOAD(state,version) : Télécharge l'état spécifié depuis la base de données.
Run Control
Nous avons interfacé notre contrôle complet d'acquisition de données dans l'objet combrc
. Le script daq_webaccess
démarre un serveur sur le port 27027
et sur l'hôte spécifié par la variable $SERVER_NAME
. Il gère une std:: map
d’objets combrc_threaded
indexés par leur nom et dispose des méthodes suivantes :
- REGISTERDAQ(daqmongo, pnsname, location) : crée un objet
combrc
en téléchargeant la configuration spécifiée dansdaqmongo
(comme"name:version"
) en utilisant le PNS spécifié danspnsname
et un nom d'expériencelocation
. Il remplit unestd:: map
combrc avecdaqmongo
comme clé. - DAQLIST() : retourne la liste des
daq
enregistrés. - REMOVEDAQ(daq) : supprime l'entrée de la map
combrc
ayant la clédaq
. - RESTART(daq) : redémarre tous les démons PMDAQ associés à la clé
daq
et vide le PNS en conséquence. - JC_DESTROY(daq) : supprime l'entrée de la clé
daq
de la map et vide le PNS en conséquence. - PNS_LIST(daq) : exécute
PNS/LIST
pour la clédaq
spécifiée. - PNS_SESSION(daq) : exécute
PNS/SESSION
pour la clédaq
spécifiée. - JC_INFO(daq) : affiche toutes les informations des services pour la clé
daq
spécifiée. - STATE(daq) : retourne l’état
daqrc
de la clédaq
spécifiée. - CREATE(daq) : obsolète.
- INITIALISE(daq, delay) : envoie une transition
initialise
pour la clédaq
, avec éventuellement un délai après l'initialisation de la carte de déclenchement. - CONFIGURE(daq) : envoie une transition
configure
pour la clédaq
. - START(daq, comment) : envoie une transition
start
pour la clédaq
avec un commentaire obligatoire pour la traçabilité. - STOP(daq) : envoie une transition
stop
pour la clédaq
. - DESTROY(daq) : envoie une transition
destroy
pour la clédaq
. - BUILDERSTATUS(daq) : retourne le statut JSON du
event builder
. - SOURCESTATUS(daq) : retourne le statut JSON de la source de données de la clé
daq
. - TRIGGERSTATUS(daq) : retourne le statut JSON de la carte de déclenchement.
- RESUME(daq) : reprend le déclenchement et l’acquisition de données pendant une session.
- PAUSE(daq) : suspend le déclenchement et l’acquisition de données pendant une session.
- PROCESS(daq, method, app, params) : envoie une commande du
daq
pour le plugin nomméapp
et la commandemethod
avec les paramètres spécifiés dansparams
. - DOWNLOADDB(daq, app, state, version) : envoie une commande
DOWNLOADDB
pour ledaq
spécifié sur tous les plugins nommésapp
, avec l’état et la version spécifiés. - LUTCALIB(daq, tdc, nchannels) : calibration FEBV1.
- LUTMASK(daq, tdc, mask, feb) : réglages FEBV1.
- SETTDCMODE(daq, mode) : mode FEBV1.
- SETTDCDELAYS(daq, active, dead) : FSM FEBV1.
- SETDIFCTRL(daq, ctrlreg) : anciens réglages DIF.
Ce service peut ainsi gérer plusieurs acquisition en simultané.
Pages dynamiques en JavaScript
Ces deux serveurs SPYNE offrent de larges capacités pour accéder à la base de données (DB) et contrôler plusieurs sessions d’acquisition (daq) à distance.
Le logiciel côté client dans les pages HTML devient simple, et la centralisation du contrôle d’exécution permet à plusieurs clients de se connecter et d’observer la même session.
Un exemple complet est disponible sur GitHub dans le répertoire scripts/web
(webdaq.html
, webdaq.js
).
Il s’exécute sur un serveur Apache.
Système de journalisation des erreurs
Logiciel
La journalisation des erreurs est basée sur log4cxx [❓].
Pour chaque bibliothèque, nous avons défini un logger spécifique et utilisé les macros suivantes pour publier des messages.
#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
#define PM_DEBUG(a,b) LOG4CXX_DEBUG(a,__FILENAME__<<"("<<__LINE__<<")"<<"-"<<__FUNCTION__<<" "<<b)
#define PM_INFO(a,b) LOG4CXX_INFO(a,__FILENAME__<<"("<<__LINE__<<")"<<"-"<<__FUNCTION__<<" "<<b)
#define PM_WARN(a,b) LOG4CXX_WARN(a,__FILENAME__<<"("<<__LINE__<<")"<<"-"<<__FUNCTION__<<" "<<b)
#define PM_ERROR(a,b) LOG4CXX_ERROR(a,__FILENAME__<<"("<<__LINE__<<")"<<"-"<<__FUNCTION__<<" "<<b)
#define PM_FATAL(a,b) LOG4CXX_FATAL(a,__FILENAME__<<"("<<__LINE__<<")"<<"-"<<__FUNCTION__<<" "<<b)
#define PMF_DEBUG(a,b) LOG4CXX_DEBUG(a,__FILENAME__<<"("<<__LINE__<<")"<<"-"<<__FUNCTION__<<"-"<<path()<<" "<<b)
#define PMF_INFO(a,b) LOG4CXX_INFO(a,__FILENAME__<<"("<<__LINE__<<")"<<"-"<<__FUNCTION__<<"-"<<path()<<" "<<b)
#define PMF_WARN(a,b) LOG4CXX_WARN(a,__FILENAME__<<"("<<__LINE__<<")"<<"-"<<__FUNCTION__<<"-"<<path()<<" "<<b)
#define PMF_ERROR(a,b) LOG4CXX_ERROR(a,__FILENAME__<<"("<<__LINE__<<")"<<"-"<<__FUNCTION__<<"-"<<path()<<" "<<b)
#define PMF_FATAL(a,b) LOG4CXX_FATAL(a,__FILENAME__<<"("<<__LINE__<<")"<<"-"<<__FUNCTION__<<"-"<<path()<<" "<<b)
où le premier paramètre est le logger log4cxx et le second le message sous forme de flux.
Les fonctions PM_LEVEL
publient des messages avec uniquement les informations de fichier et de ligne, comme :
### From a C++ call
### PM_INFO (_logPdaq, "this is a info message, after parsing configuration file");
INFO PMDAQ - baseServer.cc(29)-main this is a info message, after parsing configuration file
et les fonctions PMF_LEVEL sont utilisées dans les applications fsmw où le chemin du service est connu.
Par exemple, pour le plugin MBMDCC :
### PMF_INFO(_logMbmdcc," New MBMDCC found in db "<<std::hex<<ipboard<<std::dec<<" IP address "<<idif->second);
INFO PMDAQ_MBMDCC - MBMDCCManager.cc(148)-fsm_initialise-/tel_feb/lyon_mbmdcc/0/ New MBMDCC found in db cd64a8c0 IP address 192.168.100.205
INFO PMDAQ_MBMDCC - interface.cc(137)-addDevice Creating Board at address 192.168.100.205
INFO PMDAQ_MBMDCC - board.cc(8)-board Creating registeraccess at address 192.168.100.205
INFO PMDAQ_MBMDCC - interface.cc(141)-addDevice Adding registeraccess socket
INFO PMDAQ_MBMDCC - interface.cc(143)-addDevice Binding registers
INFO PMDAQ_MBMDCC - MBMDCCManager.cc(152)-fsm_initialise-/tel_feb/lyon_mbmdcc/0/ Registration done for cd64a8c0
INFO PMDAQ_MBMDCC - MBMDCCManager.cc(153)-fsm_initialise-/tel_feb/lyon_mbmdcc/0/ START Listenning
Collection des messages
Log4cxx utilise le même mécanisme de collection des messages que log4j[?]. Il est basé sur des appenders qui sont déclarés dans un fichier de configuration XML. Nous avons utilisé deux appenders : la console et le syslog. Nous pouvons définir des seuils d'erreur pour chaque logger spécifique :
<?xml version="1.0" encoding="UTF-8" ?>
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<!-- Output the log message to system console.
-->
<appender name="ConsoleAppender" class="org.apache.log4j.ConsoleAppender">
<param name="Threshold" value="INFO" />
<param name="Target" value="System.out"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %c{1} - %m%n"/>
</layout>
</appender>
<appender name="SYSLOG" class="org.apache.log4j.SyslogAppender">
<param name="Threshold" value="INFO" />
<param name="SysLogHost" value="localhost" />
<param name="Facility" value="LOCAL6" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern"
value="%-4r %-5p %c %x - %m" />
</layout>
<filter class="LevelRangeFilter">
<param name="AcceptOnMatch" value="true"/>
<param name="LevelMin" value="INFO" />
<param name="LevelMax" value="FATAL" />
</filter>
</appender>
<root>
<priority value="all" />
<appender-ref ref="ConsoleAppender"/>
<appender-ref ref="SYSLOG"/>
</root>
<category name="main" >
<priority value ="trace" />
</category>
<category name="PMDAQ_MBMDCC" >
<priority value ="warn" />
</category>
</log4j:configuration>
L'avantage principal de l'utilisation de l'appender syslog
est que les messages d'erreur provenant de différents ordinateurs peuvent être fusionnés à l'aide du démon rsyslog
sur un PC centralisé.
Un large éventail d'outils d'analyse et d'affichage existe pour analyser les messages de log, mais nous n'avons pas déployé spécifiquement l'un d'eux, car l'analyse des fichiers de messages centralisés est suffisante sur nos petits systèmes.
Assemblage d'événements (Event Building)
L'assemblage d'événements consiste à fusionner diverses sources de données qui collectent des fragments d'événement au même moment. Chaque source de données doit donc avoir un tag de localisation et un horodatage pour chaque fragment de données qu'elle fournit.
Ces fragments de données sont publiés via un zmq::socket_t
en utilisant le mécanisme PUSH/PULL et sont collectés de manière centralisée pour construire un événement, c’est-à-dire une collection de fragments de données ayant un horodatage identique. Cet horodatage peut être une horloge commune ou un numéro de trigger externe.
L'assembleur d'événements proposé est distribué de manière très simple : chaque source de données alimente une application d'assemblage d'événements en fonction de son numéro d’événement modulo le nombre d’applications d’assembleur d’événements. C’est-à-dire que si N est le nombre d’applications de construction d’événements, l’événement % N
est envoyé à l’instance builder
événement % N`.
Chaque assembleur d’événements collecte tous les paquets des sources de données disponibles et les écrit à leur réception en mémoire. Un thread séparé scanne ensuite la mémoire et construit l’événement, c’est-à-dire une collection de buffers de données provenant de chaque source de données enregistrée avec le même tag temporel. Un dernier thread traite les événements complétés et appelle les processeurs d’événements enregistrés qui peuvent écrire les événements sur disque dans un format défini par l’utilisateur ou les transférer vers un système de monitoring en ligne.
Un partitionnement géographique est également possible avec très peu d’adaptations.
La figure suivante montre un exemple de comportement : chaque source de données matérielle s’enregistre auprès de l’assembleur d’événements et publie des données synchronisées soit par une horloge commune, soit par un trigger externe. Les événements sont ensuite traités par deux processeurs d’événements : l’un les écrit sur disque, l’autre les place dans une mémoire partagée pour un usage de monitoring.
Il n’existe pas de mécanisme spécifique de retour de pression (back pressure), ZeroMQ stocke dans un tampon les messages et peut gérer les ralentissements intermittents. La plupart des systèmes de lecture modernes gèrent les débordements via des messages matériels. Néanmoins, il est facile dans PMDAQ d’implémenter un mécanisme de régulation (throttle) dans l’envoi des données, en utilisant des messages de monitoring ou des requêtes HTTP directes (commandes) vers les applications sources de données.
Les sections suivantes détaillent les outils logiciels fournis pour accomplir ces tâches.
Messages échangés
Structure des messages ZeroMQ
La messagerie ZeroMQ permet d’envoyer un message composite avec un en-tête contenant une chaîne analysable et un message séparé. Deux balises différentes sont utilisées dans PMDAQ :
- ID-detid-sourceid
Utilisée pour enregistrer la source de données émettrice auprès d’un assembleur d’événements donné. detid
est l’identifiant du détecteur : un identifiant utilisé pour marquer différentes partitions géographiques (ex. tonneau du trajectographe, bouchon d'un calorimètre ...) ou différentes versions du format de données pour un détecteur donné.-
sourceid
est l’identifiant de la source : un identifiant caractérisant la source de données (ex. numéro de carte frontale, numéro ADC ou TDC...) -
DS-detid-sourceid event bxtag
Utilisé pour envoyer les données de lecture à l’assembleur d’événements. event
est le numéro de l’événement (c’est-à-dire le numéro de trigger ou la séquence de lecture)bxtag
est le bunch crossing : un nombre caractérisant le temps de l’événement en nombre de coups d’horloges. L’horloge est typiquement celle de l’expérience. Cela peut permettre un assemblage plus fin des événements si nécessaire.
La seconde partie du message après la chaîne d’en-tête est un pm::buffer
.
Structure du buffer PMDAQ pm::buffer
La structure de buffer échangé doit être suffisamment polyvalente pour embarquer tout type de données. Elle est composée d’un en-tête avec des informations de localisation et des horodatages, ainsi que d’un tampon d’octets de taille variable.
La classe pm::buffer
est une structure de données simple qui fournit un espace pour stocker les données lues (la charge utile) et quatre balises :
- Identifiant du détecteur (4 octets)
- Identifiant de la source (4 octets)
- Numéro d’événement (4 octets)
- Numéro de bunch crossing (8 octets)
- Taille des données utiles : 4 octets indiquant la taille du tableau des données utiles.
- Données utiles : un tableau d’octets contenant les données du détecteur pour le numéro d’événement donné.
Les données utiles peuvent être compressées et décompressées à la volée à l’aide des appels de la bibliothèque gzip[4]
, via deux méthodes fournies : compress
et uncompress
. Le statut de ces données (compressées ou non) est stocké dans le bit 31 du mot detectorID
.
Les buffers n’ont pas besoin d’être alloués par les source de données, puisqu’ils sont intégrés dans la classe pmSender
décrite ci-dessous.
Côté serveur : La classe pmSender
L’utilisation des sockets ZeroMQ est encapsulée dans un objet dédié. C’est le seul objet dont les développeurs ont besoin pour se connecter et formater leurs données vers l'Event Builder. Un objet pmSender
instancie un pm::buffer
avec une taille maximale de 1 Mo. Il crée également la socket associée et la connecte au processus de fusion. Le buffer dispose de méthodes pour accéder à la charge utile, permettant au processus utilisateur de la remplir.
Le processus serveur peut instancier autant d’objets pm::pmSender
que nécessaire, remplir les buffers associés, et les publier à chaque fois qu’un nouvel événement est collecté. Le pseudo-code suivant montre un exemple d’utilisation :
Initialisation
// Créer un contexte zmq unique pour l'application
_context = new zmq::context_t();
// Et un Sender par source de données
unsigned int detid = 100, sourceid = 21;
// Soit en se connectant directement à une adresse unique
pm::pmSender* ds = new pm::pmSender(_context, detid, sourceid);
On peut créer une connexion unique vers un Event Builder écoutant :
ds->connect(eventBuilderListeningAddress);
std::string eventBuilderListeningAddress = "tcp://mypc:4545";
Ou, si le sender
est instancié dans un objet fsmw
, utiliser la configuration de session et le PNS pour trouver toutes les instances de l’application evb_builder
et le port TCP sur lequel l’assembleur écoute (collectingPort
). Le pmSender
crée une socket vers chaque application correspondante, triée par numéro d’instance :
push->autoDiscover(session(), "evb_builder", "collectingPort");
pm::pmSender* push = new pm::pmSender(_context, x->detectorId(), x->status()->id);
Enfin, le pmSender
envoie un message ID
à tous les Event Builders connectés :
push->collectorRegister();
Lecture (Readout)
Dans la boucle de lecture, l’application doit simplement copier ses données dans la zone de données utiles associée à la source pmSender
et ensuite publier les données avec le numéro d’événement, le numéro d’horloge (bx) et la taille des données utiles.
// Dans la boucle de lecture
// Accéder à la charge utile du buffer pmSender
uint8_t* pld = (uint8_t*) push->payload();
// Copier les données (ou écrire directement dans le buffer)
memcpy(pld, mydata, eventSize);
// Publier la source de données
push->publish(_event, bx, eventSize);
Par défaut, la compression des données est activée. Il n’y a aucune limitation sur le nombre de sources de données gérées par un processus, et la publication est thread-safe.
Event builder, côté client
Côté client, le Event builder est divisé en plusieurs fonctions :
La collecte de données
L'objet pm::pmPuller
lie le port du socket où toutes les sources de données sont connectées et effectue un polling sur toutes les sockets pour recevoir de nouveaux fragments de données. Il gère l'enregistrement des sources de données. Lors de la réception complète d'un message, il appelle une méthode virtuelle pour traiter les données qui est implémentée dans sa classe fille pm::pmMerger
.
L'organisation du processus et la construction des événements
L'objet pm::pmMerger
organise la collecte des données :
- Il gère l'exécution du thread de polling et l'enregistrement de tous les pm::evbprocessor
.
- Il implémente la méthode de traitement des buffers et stocke les buffers dans une std::map
indexée par événement.
- La map est indexée avec le numéro d'événement de pm::Buffer
reçu et le nombre de sources de données est le nombre de messages d'enregistrement reçus (ID-detid-sourceid
dans l'en-tête).
- Lorsque toutes les sources de données enregistrées ont fourni leur buffer pour un événement donné, celui-ci est traité par tous les processeurs enregistrés puis retiré de la map.
- Les événements incomplets sont purgés de la map après un certain délai (en numéro d’événement).
Le traitement des données
Le traitement des données est réalisé via l’implémentation du pm::evbprocessor
générique sous forme de plugin Linux qui peut être chargé dynamiquement lors de l’initialisation du Event Builder. Plusieurs processeurs peuvent être chargés et sont exécutés séquentiellement. La méthode principale traite un événement sous forme d'une liste complète de buffers reçus des sources de données (pmSender
). Une méthode supplémentaire permet à l'utilisateur de traiter des en-têtes de run spécifiques où des informations sur le run, des valeurs de calibration par exemple, peuvent être transmises à l’analyse.
Deux processeurs par défaut sont fournis :
- binaryWriter
qui écrit les événements compressés directement sur disque.
- shmwriter
qui écrit jusqu’à 50 événements dans une mémoire partagée, utilisable par tout processus de surveillance ou de débogage.
L’application evb_builder
Le pm::builder::collector
est l'application fsmw
qui instancie le pmMerger
. Ses paramètres sont utilisés pour configurer le merger, les processeurs et les applications émettrices via le mécanisme autoDiscover
. Sa machine à états finis est utilisée pour démarrer et arrêter les périodes de prise de données (runs). Une configuration typique est
{
"host": "lyocms09.in2p3.fr",
"instance": 0,
"name": "evb_builder",
"params": {
"collectingPort": 5556,
"directory": "/data/local/TEL_CMS/",
"processor": [
"proc_binarywriter"
],
"purge": 0
},
"port": 7777
}
Le collectingPort
est obligatoire pour permettre aux pmSenders
de découvrir cette instance de evb_builder
. Le directory
est le chemin utilisé par le processeur proc_binarywriter
pour écrire les données. Le tag purge indique que la purge des événements incomplets est désactivée.
Limitations
Comme déjà mentionné, il est assez facile de construire des événements sur une base géographique puisque les sources de données découvrent leur constructeur (builder) via son nom et son port d’écoute. Ainsi, on peut avoir un constructeur d’événements par type de source de données.
La principale limitation de notre constructeur d’événements est l’absence de contrôle (back pressure) du constructeur vers le système de déclenchement (trigger). On peut imaginer un système de gestionnaire d’événements (Event Manager) analogue à celui du DAQ de CMS, fournissant des jetons aux sources de données ou aux cartes de déclenchement, et les récupérant depuis le constructeur d’événements. Mais pour des systèmes relativement petits (tests en faisceau, prototypes), la solution la plus simple est d’augmenter le nombre d’instances de constructeur d’événements, avec la contrainte que les fichiers écrits devront ensuite être fusionnés.
Applications DAQ de Lyon
Dans cette section, nous allons passer en revue les différentes cartes utilisées dans les systèmes d'acquisition Calice/CMS Muon à Lyon. Il existe essentiellement trois types :
- les cartes de déclenchement qui distribuent les fenêtres d'acquisition aux cartes d'acquisition (front-end) et gèrent les mécanismes de régulation via des signaux "busy" (occupé),
- les cartes d'acquisition lisant les ASICs HardRoc2[?] ou MicroROC[?],
- les cartes d'acquisition lisant les ASICs PetiROC[?].
Les paramètres de configuration de ces ASICs sont stockés dans la base de données MongoDB et sont accessibles et modifiables à l'aide de scripts Python décrits ci-dessous.
Les cartes de déclenchement
Dans l'acquisition Calice, il y avait à l'origine une carte centrale de déclenchement et de collecte de données (LDA) et quelques cartes satellites, les cartes concentratrices de données (DCC), qui devaient propager le signal de déclenchement et transmettre les données et le signal "busy" à la carte centrale. Ce schéma a été abandonné mais nous avons conservé les cartes DCC comme hubs pour propager passivement les signaux de déclenchement et de "busy" vers et depuis les cartes d’acquisition.
Le firmware d’une DCC particulière a été modifié pour devenir une DCC Maître (Master DCC) qui contrôle le déclenchement, les fenêtres d’acquisition et collecte les signaux "busy" des cartes d’acquisition. Cette carte était initialement au format VME avec un contrôle via une connexion USB2 (puce FTDI[?]). Une mise à jour a été conçue à Lyon avec une carte au format NIM et une interface TCP/IP (MBMDCC avec un chip WIZNET[?]).
Principes
Chaque carte d’acquisition (DIF, GRIC, FEBV1, FC7 pour FEBV2) est connectée à la MDCC (ou DCC→MDCC) et reçoit d’elle l’horloge, une fenêtre d’acquisition et éventuellement un déclenchement. La boucle d’acquisition est gérée par la carte de synchronisation MDCC :
- Une fenêtre est ouverte
- Lorsqu’un ASIC est occupé, un signal est propagé à la carte de synchronisation (MDCC), la fenêtre d’acquisition est fermée et toutes les données des cartes d’acquisition sont lues (via USB, Ethernet ou GBT/IPBUS) et envoyées à l’
event_builder
, identifiées par le compteur de fenêtres (événement) et l’horloge (bx) - Si aucune carte n’est occupée, la fenêtre est fermée après un temps prédéfini et la même lecture a lieu
La figure suivante illustre cette boucle. La fenêtre d’acquisition peut se terminer soit sur un signal "busy" de l’acquisition, soit après une durée prédéfinie.
De plus, les cartes peuvent gérer trois signaux d'entrée :
-
un déclenchement TTL d'une durée au moins supérieure à la période d'horloge. Il sera propagé via la DCC vers la carte d'acquisition frontale qui peut alors générer un signal busy (PMR/GRIC) ou l’échantillonner (FEB)
-
un signal TTL utilisé lors des tests faisceaux (PS/SPS) qui correspond au début du spill (déversement d'un paquet) de l’accélérateur. Les fenêtres peuvent être activées uniquement pendant le spill ou à partir du début pour une durée fixe
-
un signal TTL utilisé lors des tests faisceaux (PS/SPS) qui correspond à la fin du spill (EOS)
Utilisation
En fait trois versions différentes existent sur des cartes différentes:
- MDCC , basée sur une carte DCC
- MBMDCC
- IPDC
Les registres de chacune de ces versions sont décrits ci-dessous, néanmoins les fonctionnalités sont similaires.
Registres
Carte MDCC
Adresse | Nom | Contenu |
---|---|---|
0001 | ID | Identifiant de la carte |
0002 | software_veto | Bit 0 : génère un signal d'occupation logiciel |
0003 | spillNb | Nombre de spills |
0004 | Control | Bit 0 : réinitialise le registre de déclenchement |
0005 | spillon | Durée de la fenêtre en horloges de 40 MHz |
0006 | spilloff | Délai en horloges de 40 MHz avant la prochaine fenêtre |
0007 | beam (obsolète) | Durée du spill du faisceau |
0008 | Calib | Bit 0 : non utilisé Bit 1 : Passe en mode calibration avec décrémentation du compteur de fenêtres Bit 2 : Recharge le compteur de calibration avec nb_window |
0009 | Calib_Counter | Nombre de fenêtres pour la calibration |
000A | nb_windows | Nombre de fenêtres pour l'acquisition |
000B | software_ECALveto (obsolète) | Deuxième veto logiciel |
000C | Rstdet | Envoie un RESET sur HDMI vers l'acquisition frontale |
000D | WindowConfig | Bit 0 : du début à la fin du spill Bit 1 : début du spill et durée définie par SpillOn Bit 2 : Compteur interne Bit 3 : Calibration avec SpillOn/Off contrôlé par calib Bit 4 : SOS et compteur interne (usage obsolète) Bit 5 : Compteur interne démarrant à la fin du signal d'occupation (usage obsolète) Bit 6 : Compteur interne démarrant à la fin du signal d'occupation + SpillOff (usage obsolète) |
000E | TrigExtDelay | Délai pour envoyer le trig externe |
000F | TrigExtLength | Durée de l'impulsion du trig externe |
0011 | busy1Nb | Nombre de signaux d'occupation HDMI 1 |
0012 | busy2Nb | Nombre de signaux d'occupation HDMI 2 |
0013 | busy3Nb | Nombre de signaux d'occupation HDMI 3 |
0014 | busy4Nb | Nombre de signaux d'occupation HDMI 4 |
0015 | busy5Nb | Nombre de signaux d'occupation HDMI 5 |
0016 | busy6Nb | Nombre de signaux d'occupation HDMI 6 |
0017 | busy7Nb | Nombre de signaux d'occupation HDMI 7 |
0018 | busy8Nb | Nombre de signaux d'occupation HDMI 8 |
0019 | busy9Nb | Nombre de signaux d'occupation HDMI 9 |
001A | Enable_busy_on_trigger | Génère un signal d'occupation sur déclenchement externe |
0020 | debounceBusy | Réglage de la durée minimale du signal d'occupation |
0100 | version | Version du firmware |
Carte MBMDCC
Adresse | Nom | Contenu |
---|---|---|
0000 | test_reg | Registre de lecture-écriture pour tester l'accès (valeur par défaut 1234) |
0001 | ID_reg | Identifiant de la carte |
0002 | software_veto_reg | Bit 0 : génère un busy logiciel |
0003 | spill_counter_reg | Nombre de spills |
0004 | Counters_reset_reg | Bit 0 : réinitialise le registre de déclenchement |
0005 | spillon_reg | Durée en cycles d'horloge de 40 MHz de la fenêtre |
0006 | spilloff_reg | Délai en cycles d'horloge de 40 MHz avant la prochaine fenêtre |
0008 | Calib_reg | Bit 0 : non utilisé Bit 1 : Passe en mode calibration avec décrémentation du compteur de fenêtres Bit 2 : Recharge le compteur de calibration avec nb_window |
000A | nb_windows_reg | Nombre de fenêtres pour l'acquisition |
000C | Rstdet_reg | Envoie RESET sur HDMI vers le front-end |
000D | WindowConfig_reg | Bit 0 : du début à la fin du spill Bit 1 : début du spill et longueur définie par SpillOn Bit 2 : Compteur interne Bit 3 : Calibration avec SpillOn/Off contrôlé par calib Bit 4 : SOS et compteur interne (usage obsolète) Bit 5 : Compteur interne démarrant à la fin du busy (usage obsolète) Bit 6 : Compteur interne démarrant à la fin du busy + SpillOff (usage obsolète) |
000E | TrigExtDelay_reg | Délai pour envoyer le trig externe |
000F | TrigExtLength_reg | Durée de l'impulsion de trig externe |
0010 | trigextConfig_reg | Configuration du déclenchement externe Bit 0 :Trig ext masqué si la carte est occupée Bit 1 :Trig ext arrêté si un busy arrive |
0011 | trigextcounter_reg | Compteur de déclenchement externe |
001D | clk_enable_reg | Masque de bits pour l'activation des horloges par HDMI (valeur par défaut 0x3FF) |
0020 | minBusy_reg | Temps minimal de busy |
0021 | channel_enable_reg | Masque de bits pour l'activation des canaux HDMI |
0030 | TDC_Ctrl_reg | Non utilisé (à faire) |
0031 | TDC_CoarseCount_reg | Non utilisé (à faire) |
0032 | TDC_time1_reg | Non utilisé (à faire) |
0033 | TDC_clkcnt1_reg | Non utilisé (à faire) |
0034 | TDC_time2_reg | Non utilisé (à faire) |
0035 | TDC_clkcnt2_reg | Non utilisé (à faire) |
0036 | TDC_time3_reg | Non utilisé (à faire) |
0037 | TDC_clkcnt3_reg | Non utilisé (à faire) |
0038 | TDC_time4_reg | Non utilisé (à faire) |
0039 | TDC_clkcnt4_reg | Non utilisé (à faire) |
003a | TDC_time5_reg | Non utilisé (à faire) |
003b | TDC_clkcnt5_reg | Non utilisé (à faire) |
003c | TDC_time6_reg | Non utilisé (à faire) |
003d | TDC_clkcnt6_reg | Non utilisé |
0040 | TDC_calib1_reg | Non utilisé (à faire) |
0041 | TDC_calib2_reg | Non utilisé (à faire) |
0050-5B | busy0/B_counter_reg | Registres de compteurs de busy de 0 à 11 |
0070 | sps_spill_duration_reg | Durée en cycles d'horloge du spill SPS |
0071 | sps_spill_control_reg | Bit 0 : Active le SPS fill comme Not Busy Bit 1 : Arrête le SPS fill si un busy arrive |
00A0 | LemoMask_reg | Bit 0 : Début du spill Bit 1 : Fin du spill Bit 2 : Déclenchement externe Bit 3 : Spill SPS |
0100 | version | Version du firmware |
0200 | feb_trg_ctrl_reg | Bit 0 : Déclenchement externe normal envoyé Bit 1 : Déclenchement généré depuis le début de la fenêtre |
0201 | feb_trg_duration_reg | Durée de l'impulsion de déclenchement FC7 |
0202 | feb_trg_delay_reg | Délai de l'impulsion de déclenchement FC7 |
Carte IPDC
Address | Name | Content |
---|---|---|
0000 | test_reg | Registre de lecture-écriture pour tester l'accès (par défaut 1234) |
0001 | ID_reg | Identifiant de la carte |
0002 | software_veto_reg | Bit 0 : génère un busy logiciel |
0003 | spill_counter_reg | Nombre de spill |
0004 | Counters_reset_reg | Bit 0 : réinitialise le registre de déclenchement |
0005 | spillon_reg | Durée en horloge de 40 MHz de la fenêtre |
0006 | spilloff_reg | Délai en horloge de 40 MHz de la prochaine fenêtre |
0008 | Calib_reg | Bit 0 : non utilisé Bit 1 : Passe en mode calibration avec décrémentation du compteur de fenêtres Bit 2 : Recharge le compteur de calibration avec nb_window |
000A | nb_windows_reg | Nombre de fenêtres pour l'acquisition |
000C | Rstdet_reg | Envoie un RESET sur HDMI vers l'avant de la carte |
000D | WindowConfig_reg | Bit 0 : début du spill jusqu'à la fin du spill Bit 1 : début du spill et longueur de SpillOn Bit 2 : Compteur interne Bit 3 : calibration avec contrôle SpillOn/Off par calib Bit 4 : SOS et comptage interne (usage obsolète) Bit 5 : Démarrage du comptage interne sur End Busy (usage obsolète) Bit 6 : Démarrage du comptage interne sur End Busy + SpillOff (usage obsolète) |
000E | TrigExtDelay_reg | Délai pour envoyer le trigext |
000F | TrigExtLength_reg | Longueur de l'impulsion de trig ext |
0010 | trigextConfig_reg | Configuration du déclencheur externe Bit 0 : Déclenchement externe masqué si la carte est occupée Bit 1 : Déclenchement externe arrêté si un busy arrive |
0011 | trigextcounter_reg | Compteur de déclenchements externes |
0020 | minBusy_reg | Temps minimal de busy |
0021 | channel_enable_reg | Bit 0 : DIF central Bit 1 : DIF gauche Bit 2 : DIF droit |
0050-5B | busy0/B_counter_reg | De 0 à 11 compteurs de busy (seulement 3 utilisés) |
00A0 | LemoMask_reg | Masque du déclencheur externe sur Lemo (1 → Actif) |
0100 | version | Version du firmware |