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:

Un exemple de machine d'état

  1. É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.
  2. 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.

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 gérées par les services suivants.

  • /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 avec des méthodes analogues à celles des plugins.

-/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.

API link

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 :

  1. Il organise l'exécution cohérente des différentes applications (initialisation, configuration, exécution, gestion des enregistrements, etc.).
  2. 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 commande name avec content en paramètre si elle est disponible.
  • sendTransition(name, content): Envoie la transition name avec content 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()

  1. Obligation de redéfinir les méthodes :
  2. Ces cinq méthodes (daq_initialising, daq_configuring, etc.) doivent être déclarées et implémentées dans la classe qui hérite de daqControl.

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 dans daqmongo (comme "name:version") en utilisant le PNS spécifié dans pnsname et un nom d'expérience location. Il remplit une std:: map combrc avec daqmongo 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 commande method avec les paramètres spécifiés dans params.
  • DOWNLOADDB(daq, app, state, version) : envoie une commande DOWNLOADDB pour le daq spécifié sur tous les plugins nommés app, 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 rsyslogsur 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.

EventBuilderZdaq

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 pmSenderet 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.

Schéma en temps de la boucle d'acquisition

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