From: <ro...@us...> - 2014-07-23 14:17:56
|
Revision: 3659 http://sourceforge.net/p/nscldaq/code/3659 Author: ron-fox Date: 2014-07-23 14:17:53 +0000 (Wed, 23 Jul 2014) Log Message: ----------- * Feature #1962 - Control panel for state manager. * Feature #1966, Feature #1967 - Mechanism to attach readouts to the state manager and have them start/stop runs in response to appropriate state transitions. * Feature #2117 Add to SBSReadout the ability to report failures when acquiring data to a handler in the main thread interpreter. Add code to the Tcl state manager awareness package so that failures in transitions and failures reported by the trigger loop end any run in progress and request a FAIL state transition. Modified Paths: -------------- branches/nscldaq-11.0-development/ChangeLog branches/nscldaq-11.0-development/configure.ac branches/nscldaq-11.0-development/sbs/readout/CExperiment.cpp branches/nscldaq-11.0-development/sbs/readout/CExperiment.h branches/nscldaq-11.0-development/sbs/readout/CReadoutMain.cpp branches/nscldaq-11.0-development/sbs/readout/CTriggerLoop.cpp branches/nscldaq-11.0-development/sbs/readout/CTriggerLoop.h branches/nscldaq-11.0-development/sbs/readout/Makefile.am branches/nscldaq-11.0-development/sbs/readout/SBSReadout.xml branches/nscldaq-11.0-development/sbs/readout/options.ggo branches/nscldaq-11.0-development/utilities/Makefile.am branches/nscldaq-11.0-development/utilities/statemanager/Makefile.am Added Paths: ----------- branches/nscldaq-11.0-development/sbs/readout/readoutStateHook.tcl branches/nscldaq-11.0-development/utilities/cpanel/ branches/nscldaq-11.0-development/utilities/cpanel/Makefile.am branches/nscldaq-11.0-development/utilities/cpanel/cpanel-active.jpg branches/nscldaq-11.0-development/utilities/cpanel/cpanel-notready.jpg branches/nscldaq-11.0-development/utilities/cpanel/cpanel-ready.jpg branches/nscldaq-11.0-development/utilities/cpanel/cpanel.py branches/nscldaq-11.0-development/utilities/cpanel/cpanel.xml branches/nscldaq-11.0-development/utilities/cpanel/cpanelWidget.py Modified: branches/nscldaq-11.0-development/ChangeLog =================================================================== --- branches/nscldaq-11.0-development/ChangeLog 2014-07-22 18:35:38 UTC (rev 3658) +++ branches/nscldaq-11.0-development/ChangeLog 2014-07-23 14:17:53 UTC (rev 3659) @@ -639,4 +639,13 @@ addressing is not requested * Allow CAENcard to be programmed with a GEO address that is not a valid VME slot number if geo addressing is not being used. + * Sprint for experiment configuration Begin/End completed: + * Issue #1962 - Control panel for state manager. + * Issue #1966, Issue $1967 Users can use the control panel to + start and stop runs. + * Issue #2117 - State aware readouts report state transition + failures as well as failures in the active state to the + statemanager as FAIL transition requests. + * Issue #2122 - Documentation of the elements created/changed in + the experiment configuration Begin/End sprint. Modified: branches/nscldaq-11.0-development/configure.ac =================================================================== --- branches/nscldaq-11.0-development/configure.ac 2014-07-22 18:35:38 UTC (rev 3658) +++ branches/nscldaq-11.0-development/configure.ac 2014-07-23 14:17:53 UTC (rev 3659) @@ -369,6 +369,7 @@ utilities/experimentConfiguration/Makefile utilities/statemanager/Makefile utilities/boot/Makefile + utilities/cpanel/Makefile epics/chanlog/Makefile epics/controlpush/Makefile epics/epicsdisplay/Makefile Modified: branches/nscldaq-11.0-development/sbs/readout/CExperiment.cpp =================================================================== --- branches/nscldaq-11.0-development/sbs/readout/CExperiment.cpp 2014-07-22 18:35:38 UTC (rev 3658) +++ branches/nscldaq-11.0-development/sbs/readout/CExperiment.cpp 2014-07-23 14:17:53 UTC (rev 3659) @@ -37,6 +37,8 @@ #include <CVariableBuffers.h> #include <TCLApplication.h> +#include <TCLInterpreter.h> +#include <TCLObject.h> #include <CBusy.h> #include <vector> #include <string> @@ -61,6 +63,11 @@ } EndRunEvent, *pEndRunEvent; +typedef struct _TriggerFailEvent { + Tcl_Event tcl_Event; + char* message; +} TriggerFailEvent, *pTriggerFailEvent; + extern CTCLApplication* gpTCLApplication; // We need this to get the tcl interp. /////////////////////////////////////////////////////////////////////////////////////////// @@ -731,3 +738,97 @@ m_nSourceId = id; m_needHeader = true; } +/** + * triggerFail + * Called by the trigger thread if it caught an exceptino that caused the + * trigger loop to exit. In that case we schedule an event for the main thread + * (HandleTriggerLoopError). The event will attempt to invoke the + * command ::onTriggerFail passing the message we got from the trigger loop. + * That command can do whatever it wants (I recommend at least ending the run). + * + * @param msg - A text message from the trigger loop that describes the + * error. + */ +void +CExperiment::triggerFail(std::string msg) +{ + // Create and fill in the event struct: + + pTriggerFailEvent pEvent = + reinterpret_cast<pTriggerFailEvent>(Tcl_Alloc(sizeof(TriggerFailEvent))); + + pEvent->tcl_Event.proc = HandleTriggerLoopError; + pEvent->message = Tcl_Alloc(msg.size() + 1); + strcpy(pEvent->message, msg.c_str()); + + // figure out which thread is our target and schedule the event. + + Tcl_ThreadId threadId = gpTCLApplication->getThread(); + Tcl_ThreadQueueEvent(threadId, reinterpret_cast<Tcl_Event*>(pEvent), TCL_QUEUE_TAIL); + + +} +/** + * HandleTriggerLoopError + * Runs in the main thread. Receives events that report errors in the trigger + * thread. Here's what we try to do: + * * First try to call ::onTriggerFail passing it the message. + * * If that fails invoke bgerror again, passing it the message. + * * Finally release storage associated with the message and return 1 + * indicating the event dispatcher can release the event. + * @param pEvent - actually a pointer to a TriggerFailEvent struct. + * @param flags - event flags. + * @return int - (1) - indicates the event storage can be disposed of. + * + */ +int +CExperiment::HandleTriggerLoopError(Tcl_Event* pEvent, int flags) +{ + // Obtain the message and delete it's storage so we won't forget: + + pTriggerFailEvent pTfEvent = reinterpret_cast<pTriggerFailEvent>(pEvent); + std::string msg(pTfEvent->message); + Tcl_Free(pTfEvent->message); + pTfEvent->message = 0; // Ensure it's never referenced. + + // Give ::onTriggerFail a try: + + CTCLInterpreter* pInterp = gpTCLApplication->getInterpreter(); + CTCLObject command = createCommand(pInterp, "::onTriggerFail", msg); + int stat = Tcl_GlobalEvalObj( + pInterp->getInterpreter(), command.getObject() + ); + + // If that failed fall back on bgerror: + + if (stat != TCL_OK) { + command = createCommand(pInterp, "bgerror", msg); + Tcl_GlobalEvalObj( + pInterp->getInterpreter(), command.getObject() + ); + } + // Return such that the event can be released: + + return 1; + +} +/** + * createCommand + * Create a CTCLObject that is a verb an a parameter. + * + * @param pInterp - pointer to the Tcl encapsulated interpreter. + * @param verb - command verb. + * @param parameter - Command parameter. + */ +CTCLObject +CExperiment::createCommand( + CTCLInterpreter* pInterp, const char* verb, std::string parameter +) +{ + CTCLObject command; + command.Bind(pInterp); + command += verb; + command += parameter; + + return command; +} Modified: branches/nscldaq-11.0-development/sbs/readout/CExperiment.h =================================================================== --- branches/nscldaq-11.0-development/sbs/readout/CExperiment.h 2014-07-22 18:35:38 UTC (rev 3658) +++ branches/nscldaq-11.0-development/sbs/readout/CExperiment.h 2014-07-23 14:17:53 UTC (rev 3659) @@ -44,6 +44,12 @@ #endif #endif +class CTCLInterpreter; + +#ifndef _TCLOBJECT_H +#include <TCLObject.h> +#endif + // Forwared definitions: class RunState; @@ -56,6 +62,7 @@ class CEventSegment; class CScaler; + struct gengetopt_args_info; /*! @@ -96,6 +103,7 @@ bool m_needHeader; uint16_t m_nDefaultSourceId; + // Canonicals: public: @@ -140,11 +148,16 @@ } void setTimestamp(uint64_t stamp); void setSourceId(uint32_t id); + void triggerFail(std::string msg); + void syncEndRun(bool pause); private: void readScalers(); - void syncEndRun(bool pause); + static int HandleEndRunEvent(Tcl_Event* evPtr, int flags); + static int HandleTriggerLoopError(Tcl_Event* evPtr, int flags); + static CTCLObject createCommand( + CTCLInterpreter* pInterp, const char* verb, std::string parameter); static uint64_t getTimeMs(); }; Modified: branches/nscldaq-11.0-development/sbs/readout/CReadoutMain.cpp =================================================================== --- branches/nscldaq-11.0-development/sbs/readout/CReadoutMain.cpp 2014-07-22 18:35:38 UTC (rev 3658) +++ branches/nscldaq-11.0-development/sbs/readout/CReadoutMain.cpp 2014-07-23 14:17:53 UTC (rev 3659) @@ -141,11 +141,23 @@ // commands from stdin: if (parsedArgs.port_given) { - startTclServer(string(parsedArgs.port_arg)); + startTclServer(string(parsedArgs.port_arg)); } + // If an initialization script was specified run it here: + + if (parsedArgs.init_script_given) { + // We need to be able to read the script: + + if(access(parsedArgs.init_script_arg, R_OK)) { + std::string msg = "Initialization script: '"; + msg += parsedArgs.init_script_arg; + msg += "' could not be read"; + throw msg; + } + getInterpreter()->EvalFile(parsedArgs.init_script_arg); + } - // Setup our eventloop. Modified: branches/nscldaq-11.0-development/sbs/readout/CTriggerLoop.cpp =================================================================== --- branches/nscldaq-11.0-development/sbs/readout/CTriggerLoop.cpp 2014-07-22 18:35:38 UTC (rev 3658) +++ branches/nscldaq-11.0-development/sbs/readout/CTriggerLoop.cpp 2014-07-23 14:17:53 UTC (rev 3659) @@ -69,11 +69,13 @@ if (!m_running) { m_running = false; m_stopping = false; + m_failed = false; Thread::start(); // Now wait here until we know the thread is running - while (!m_running) { + + while (!m_running && !m_failed) { Os::usleep(500); } } @@ -93,8 +95,15 @@ CTriggerLoop::stop(bool pausing) { if (runningThread() != getId()) { - m_stopping = true; - m_pausing = pausing; + if (!m_failed) { + m_stopping = true; + m_pausing = pausing; + } else { + // Trigger loop failed... + + m_pausing = pausing; + m_pExperiment->syncEndRun(pausing); + } } else { char mypid[1000]; @@ -119,8 +128,47 @@ m_running = true; m_stopping = false; - mainLoop(); + // On exceptions notify the experiment: + + try { + mainLoop(); + } + catch (const char* msg) { + m_pExperiment->triggerFail(msg); + m_running = false; + m_stopping = false; + m_failed = true; + throw; + } + catch (std::string msg) { + m_pExperiment->triggerFail(msg); + m_running = false; + m_stopping = false; + m_failed = true; + throw; + + } + catch (CException& e) { + std::string msg = e.ReasonText(); + msg += " "; + msg += e.WasDoing(); + m_pExperiment->triggerFail(msg); + m_running = false; + m_stopping = false; + m_failed = true; + throw; + + } + catch (...) { + m_pExperiment->triggerFail("Unexpected exception caught"); + m_running = false; + m_stopping = false; + m_failed = true; + throw; + + } + m_running = false; m_stopping = false; } Modified: branches/nscldaq-11.0-development/sbs/readout/CTriggerLoop.h =================================================================== --- branches/nscldaq-11.0-development/sbs/readout/CTriggerLoop.h 2014-07-22 18:35:38 UTC (rev 3658) +++ branches/nscldaq-11.0-development/sbs/readout/CTriggerLoop.h 2014-07-23 14:17:53 UTC (rev 3659) @@ -47,6 +47,7 @@ volatile bool m_running; volatile bool m_stopping; // Shared between threads. volatile bool m_pausing; // Shared between threads. + volatile bool m_failed; // Shared between thread... trigger loop failed. public: CTriggerLoop(CExperiment& experiment); Modified: branches/nscldaq-11.0-development/sbs/readout/Makefile.am =================================================================== --- branches/nscldaq-11.0-development/sbs/readout/Makefile.am 2014-07-22 18:35:38 UTC (rev 3658) +++ branches/nscldaq-11.0-development/sbs/readout/Makefile.am 2014-07-23 14:17:53 UTC (rev 3659) @@ -1,6 +1,9 @@ lib_LTLIBRARIES = libSBSProductionReadout.la +TCLPACKAGES = readoutStateHook.tcl +TCLPKGDIR = @prefix@/TclLibs/readoutStateHook + BUILT_SOURCES=options.c options.h libSBSProductionReadout_la_SOURCES = \ @@ -80,7 +83,8 @@ libSBSProductionReadout_la_LDFLAGS = -version-info 1:0:0 \ @LIBTCLPLUS_LDFLAGS@ $(TCL_LDFLAGS) $(THREADLD_FLAGS) -EXTRA_DIST = options.ggo Skeleton.cpp UserMakefile.in SBSRdoMakeIncludes.in SBSReadout.xml +EXTRA_DIST = options.ggo Skeleton.cpp UserMakefile.in SBSRdoMakeIncludes.in SBSReadout.xml \ + $(TCLPACKAGES) noinst_PROGRAMS = Readout tests @@ -101,11 +105,14 @@ $(mkinstalldirs) @prefix@/skeletons/sbs $(mkinstalldirs) @prefix@/etc $(mkinstalldirs) @prefix@/include/sbsreadout + $(mkinstalldirs) $(TCLPKGDIR) $(INSTALL_SCRIPT) @srcdir@/Skeleton.cpp @prefix@/skeletons/sbs $(INSTALL_SCRIPT) @srcdir@/Skeleton.h @prefix@/skeletons/sbs $(INSTALL_SCRIPT) UserMakefile @prefix@/skeletons/sbs/Makefile $(INSTALL_SCRIPT) SBSRdoMakeIncludes @prefix@/etc $(INSTALL_SCRIPT) @srcdir@/*.h @prefix@/include/sbsreadout + for f in $(TCLPACKAGES); do $(INSTALL_SCRIPT) $$f $(TCLPKGDIR); done + echo "package ifneeded ReadoutStateHook 1.0 [list source [file join \$$dir readoutStateHook.tcl]]" > $(TCLPKGDIR)/pkgIndex.tcl options.c: options.ggo Modified: branches/nscldaq-11.0-development/sbs/readout/SBSReadout.xml =================================================================== --- branches/nscldaq-11.0-development/sbs/readout/SBSReadout.xml 2014-07-22 18:35:38 UTC (rev 3658) +++ branches/nscldaq-11.0-development/sbs/readout/SBSReadout.xml 2014-07-23 14:17:53 UTC (rev 3659) @@ -18,8 +18,16 @@ have to modify to produce a real application. </para> </listitem> - </itemizedlist> - </para> + <listitem> + <para> + How to hook the readout program into the NSCLDAQ-11.0 state + manager. See + <link linkend='chap.statemgr' endterm='chap.statemgr.title' /> + for more information about the state manager. + </para> + </listitem> + </itemizedlist> + </para> <para> Complete reference information is available in the 3sbsreadout part of this manual. @@ -898,7 +906,54 @@ the <option>--help</option> option. </para> </section> - </chapter> + <section> + <title>Using SBSReadout with the state manager</title> + <para> + The SBS Readout program can be used with the state manager, and boot + manager. This allows you to use the experiment configuration tool to + define the elements of an experiment and to have the boot manager + automatically create and destroy those elements as needed. When hooked + to the state manager, the SBSReadout program will also start a run + when the state manager indicates the state is <literal>Active</literal>, + stop any active run when the state becomes <literal>Ready</literal> and + exit if the state becomes <literal>NotReady</literal>. + </para> + <para> + The SBSReadout state manager interface is accomplished + via the <literal>ReadoutStateHook</literal> Tcl package. + This package starts up the Tcl state monitor API and registers + interest in the <literal>Active</literal> <literal>Ready</literal> + and <literal>NotReady</literal> states taking appropriate + action when those states are entered. The interface also creates a + <command>onTriggerFail</command> proc that gets control if the readout + fails when the run is active. + </para> + <para> + The key to using this package is the <option>--init-script</option> + command line option. When you describe the Readout program to the + experiment configuration editor be sure to include an + <option>--init-script</option> command line parameter that runs + an initialization script that includes the <literal>ReadoutStateHook</literal> + package. + </para> + <para> + If the <literal>DAQROOT</literal> environment variable is defined (by + sourcing the daqsetup.bash), and if Readout is run under the boot + manager, the example below will connect your readout + program to the state manager: + </para> + <example> + <title>Script to attach SBSReadout to the state manager</title> + <programlisting> +set pkgDirs [file join $env(DAQROOT) TclLibs] +lappend auto_path $pkgDirs + +package require ReadoutStateHook + + </programlisting> + </example> + </section> +</chapter> <!-- /chapter --> @@ -4347,6 +4402,16 @@ </listitem> </varlistentry> <varlistentry> + <term><option>--init-script</option>=<replaceable>file-path</replaceable></term> + <listitem> + <para> + After initialization, executes the script designated by + <replaceable>file-path</replaceable> in the main thread's + interpreter. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--help</option></term> <listitem> <para> Modified: branches/nscldaq-11.0-development/sbs/readout/options.ggo =================================================================== --- branches/nscldaq-11.0-development/sbs/readout/options.ggo 2014-07-22 18:35:38 UTC (rev 3658) +++ branches/nscldaq-11.0-development/sbs/readout/options.ggo 2014-07-23 14:17:53 UTC (rev 3659) @@ -6,3 +6,4 @@ option "port" p "Enable tcl server functionality, next parameter is the port" string no option "ring" r "Ring buffer name, defaults to your username" string no option "sourceid" i "Source Id for event builder sources" optional int default="0" +option "init-script" s "Initialization script" optional string Added: branches/nscldaq-11.0-development/sbs/readout/readoutStateHook.tcl =================================================================== --- branches/nscldaq-11.0-development/sbs/readout/readoutStateHook.tcl (rev 0) +++ branches/nscldaq-11.0-development/sbs/readout/readoutStateHook.tcl 2014-07-23 14:17:53 UTC (rev 3659) @@ -0,0 +1,153 @@ +#!/bin/sh +# -*- tcl -*- +# The next line is executed by /bin/sh, but not tcl \ +exec tclsh "$0" ${1+"$@"} + +# This software is Copyright by the Board of Trustees of Michigan +# State University (c) Copyright 2014. +# +# You may use this software under the terms of the GNU public license +# (GPL). The terms of this license are described at: +# +# http://www.gnu.org/licenses/gpl.txt +# +# Authors: +# Ron Fox +# Jeromy Tompkins +# NSCL +# Michigan State University +# East Lansing, MI 48824-1321 + + + +## +# @file readoutStateHook.tcl +# @brief Hook the readout program into the NSCL State manager. +# @author Ron Fox <fo...@ns...> +# + +package provide ReadoutStateHook 1.0 + + +## +# The big assumption for this file is that Readout is being run from +# the boot program. Under that assumption, the environment variables +# 'TRANSITION_REQUEST_URI' and 'TRANSITION_SUBSCRIPTION_URI' are +# defined to be the URI's that get us connected to the state manager. +# In addtion, 'DAQROOT' is defined to point at the top level of the +# NSCLDAQ installation directory. +# + + +# Pull in the state manager: + + +set nsclTclLibs [file join $env(DAQROOT) TclLibs] +lappend auto_path $nsclTclLibs +package require statemanager + + +#----------------------------------------------------------------------- +# Script procs...they're all going to be in the StateAwareRdo namespace: + +namespace eval ::StateAwareRdo { + set active 0 +} + +## +# ::StateAwareRdo::toActive +# +# Handle the toActive transition. This is only done if the prior state +# was Ready so that we don't jump in in the middle of a run. +# +# @param prior - previous state. +# @param state - Current state (Active). +# +proc ::StateAwareRdo::toActive {prior state} { + if {[string toupper $prior] eq "READY"} { + # + # If begin fails... go to not ready + # + if {[catch begin msg]} { + puts "Begin failed: '$msg'" + ::statemanager::statemonitor transition FAIL + return + } + set ::StateAwareRdo::active 1; # Run in progress + } +} +## +# ::StateAwareRdo::toReady +# +# Handle transitions to the Ready state. We only act if we are +# known to be coming from the active state...and the run is in progress +# This prevents us from stopping a run that is not actually in progress. +# and from acting when whe are just getting an initial state (probably +# we don't actually have to factor the prior state into this calculation). +# +# @param prior - prior state. +# @param state - Current state (Ready). +# +proc ::StateAwareRdo::toReady {prior state} { + if {([string toupper $prior] eq "ACTIVE") && ($::StateAwareRdo::active)} { + if {[catch end msg]} { + puts "End failed: '$msg'" + ::statemanager::statemonitor transition FAIL + return + } + set ::StateAwareRdo::active 0 + } +} +## +# ::StateAwareRdo::toNotReady +# +# Handles transitions to the NotReady State. NotReady is entered only +# if the program is supposed to exit: +# * If data taking is in progress, the run is ended. +# * We exit. +# +# @param prior - the prior state. +# @param state - The current state (NotReady). +# +proc ::StateAwareRdo::toNotReady {prior state} { + if {$::StateAwareRdo::active} { + catch end msg; # Don't let failure kill us. + } + exit 0; # exit the program +} +## +# onTriggerFail +# Called if the trigger loop failed. In this case: +# * End our run cleanly. +# * Fail the system. +# +# @param msg - reason for the failure. +# +proc onTriggerFail msg { + puts "The trigger loop failed: $msg" + catch end + statemanager::statemonitor transition FAIL +} +#------------------------------------------------------------------------ +# Script main entry point +# + +# Start the state manager thread/event-loop pump. + +set transURI $env(TRANSITION_REQUEST_URI) +set stateURI $env(TRANSITION_SUBSCRIPTION_URI) + +statemanager::statemonitor start $transURI $stateURI + +# Register interest in state +# We care about Active, Ready and NotReady +# + +statemanager::statemonitor register Active ::StateAwareRdo::toActive +statemanager::statemonitor register Ready ::StateAwareRdo::toReady +statemanager::statemonitor register NotReady ::StateAwareRdo::toNotReady + + + +# Falling through enters the readout program's event loop allowing poked commands +# to work too. Modified: branches/nscldaq-11.0-development/utilities/Makefile.am =================================================================== --- branches/nscldaq-11.0-development/utilities/Makefile.am 2014-07-22 18:35:38 UTC (rev 3658) +++ branches/nscldaq-11.0-development/utilities/Makefile.am 2014-07-23 14:17:53 UTC (rev 3659) @@ -4,7 +4,7 @@ SUBDIRS = scalerdisplay daqstart dvdburn \ sequencer ringselector eventlog bufdump \ sclclient tkbufdump compatibility \ - filter experimentConfiguration statemanager boot + filter experimentConfiguration statemanager boot cpanel Added: branches/nscldaq-11.0-development/utilities/cpanel/Makefile.am =================================================================== --- branches/nscldaq-11.0-development/utilities/cpanel/Makefile.am (rev 0) +++ branches/nscldaq-11.0-development/utilities/cpanel/Makefile.am 2014-07-23 14:17:53 UTC (rev 3659) @@ -0,0 +1,25 @@ +PACKAGE_FILES=cpanelWidget.py + +MAINS = cpanel.py + +FIGURES = cpanel-active.jpg cpanel-notready.jpg cpanel-ready.jpg + +INSTDIR=@prefix@/pythonLibs/nscldaq/cpanel + +## +# installation hook: create the installation directory tree. +# install the packges in INSTDIR +# ensure there's an __init__.py which is required for +# package directories. +# + +install-exec-local: + $(mkinstalldirs) $(INSTDIR) + $(mkinstalldirs) @datarootdir@/html + touch $(INSTDIR)/__init__.py + for f in $(MAINS); do $(INSTALL_SCRIPT) @srcdir@/$$f @bindir@/`basename $$f .py`; done + for f in $(PACKAGE_FILES); do $(INSTALL_SCRIPT) @srcdir@/$$f $(INSTDIR); done + for f in $(FIGURES); do $(INSTALL_DATA) @srcdir@/$$f @datarootdir@/html; done + + +EXTRA_DIST=$(PACKAGE_FILES) $(MAINS) $(FIGURES) \ No newline at end of file Added: branches/nscldaq-11.0-development/utilities/cpanel/cpanel-active.jpg =================================================================== (Binary files differ) Index: branches/nscldaq-11.0-development/utilities/cpanel/cpanel-active.jpg =================================================================== --- branches/nscldaq-11.0-development/utilities/cpanel/cpanel-active.jpg 2014-07-22 18:35:38 UTC (rev 3658) +++ branches/nscldaq-11.0-development/utilities/cpanel/cpanel-active.jpg 2014-07-23 14:17:53 UTC (rev 3659) Property changes on: branches/nscldaq-11.0-development/utilities/cpanel/cpanel-active.jpg ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +application/octet-stream \ No newline at end of property Added: branches/nscldaq-11.0-development/utilities/cpanel/cpanel-notready.jpg =================================================================== (Binary files differ) Index: branches/nscldaq-11.0-development/utilities/cpanel/cpanel-notready.jpg =================================================================== --- branches/nscldaq-11.0-development/utilities/cpanel/cpanel-notready.jpg 2014-07-22 18:35:38 UTC (rev 3658) +++ branches/nscldaq-11.0-development/utilities/cpanel/cpanel-notready.jpg 2014-07-23 14:17:53 UTC (rev 3659) Property changes on: branches/nscldaq-11.0-development/utilities/cpanel/cpanel-notready.jpg ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +application/octet-stream \ No newline at end of property Added: branches/nscldaq-11.0-development/utilities/cpanel/cpanel-ready.jpg =================================================================== (Binary files differ) Index: branches/nscldaq-11.0-development/utilities/cpanel/cpanel-ready.jpg =================================================================== --- branches/nscldaq-11.0-development/utilities/cpanel/cpanel-ready.jpg 2014-07-22 18:35:38 UTC (rev 3658) +++ branches/nscldaq-11.0-development/utilities/cpanel/cpanel-ready.jpg 2014-07-23 14:17:53 UTC (rev 3659) Property changes on: branches/nscldaq-11.0-development/utilities/cpanel/cpanel-ready.jpg ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +application/octet-stream \ No newline at end of property Added: branches/nscldaq-11.0-development/utilities/cpanel/cpanel.py =================================================================== --- branches/nscldaq-11.0-development/utilities/cpanel/cpanel.py (rev 0) +++ branches/nscldaq-11.0-development/utilities/cpanel/cpanel.py 2014-07-23 14:17:53 UTC (rev 3659) @@ -0,0 +1,189 @@ +#!/usr/bin/env python + + + +# This software is Copyright by the Board of Trustees of Michigan +# State University (c) Copyright 2013. +# +# You may use this software under the terms of the GNU public license +# (GPL). The terms of this license are described at: +# +# http://www.gnu.org/licenses/gpl.txt +# +# Author: +# Ron Fox +# NSCL +# Michigan State University +# East Lansing, MI 48824-1321 + +## +# @file cpanel.py +# @brief Control panel for state manager +# @author <fo...@ns...> + +from nscldaq.cpanel import cpanelWidget +from nscldaq.statemanager import StateMonitor +from nscldaq.statemanager import Utilities +from PyQt4 import QtGui, QtCore + +import argparse +import getpass +import os +import sys + +## +# This program creates a cpanelWidget and connects it to the state manager. +# +# The state manager is located by a fairly complex but determinitid set of rules: +# +# * Command line parameters are used to determine the host and, if supplied +# service names used by the state manager. +# * If command line parameters are not supplied environment variables that +# are supplied by the Boot program are used specifically: +# TRANSITION_REQUEST_URI', 'TRANSITION_SUBSCRIPTION_URI' +# * Finally if none of these methods provide the necessary information +# to connect to the state manager, an assumption is made that the state +# manager is running on default services in localhost. +# +# +# State manager transitions are used to update the state of the UI while +# we connect to the GUI's buttonPush signal and use our slot to intiate +# state transitions. +# + + +## +# parseArgs +# Define and parse the parameters. The we understand the following +# options only: +# * --version, -v - Print version of program and exit. +# * --server, -n - Host on which the state manager server is running. +# * --state-service, -s +# - Service name used for state manager's state/transition +# publications. +# * --transition-service, -t +# - Service name that accepts state transition requests. +# +def parseArgs(): + parser = argparse.ArgumentParser( + description='Control panel for the experiment' + ) + parser.add_argument( + '-v', '--version', help='Print program version and exit', + action='store_const', const=1 + ) + parser.add_argument( + '-s', '--state-uri', help='State publication URI' + ) + parser.add_argument( + '-t', '--transition-uri', help='Transition request URI' + ) + + return parser.parse_args() + +## +# connectStateManager +# +# Figures out the URI's for the state manager and creates the state manager +# API. +# +# @param args - Parsed command line arguments. +# @return StateMonitor - the created API to the state manager. +# +def connectStateManager(args): + # First are both URI's defined by command parameters: + + stateUri = None + transUri = None + if (args.state_uri != None) and (args.transition_uri != None): + stateUri = args.state_uri + transUri = args.trans_uri + + # If we don't have an answer yet, try the environment variables + # again we need both of them (TRANSITION_REQUEST_URI', 'TRANSITION_SUBSCRIPTION_URI') + + if stateUri == None: + stateUri = os.getenv('TRANSITION_SUBSCRIPTION_URI') + transUri = os.getenv('TRANSITION_REQUEST_URI') + if (stateUri == None) or (transUri == None): + stateUri = None + transUri = None + + # If we still don't have an answer look up the URI's in the local host. + + if stateUri == None: + statePort = Utilities.getPort('localhost', 'StatePublish', getpass.getuser()) + transPort = Utilities.getPort('localhost', 'StateRequest', getpass.getuser()) + stateUri = 'tcp://localhost:%d' % (statePort) + transUri = 'tcp://localhost:%d' % (transPort) + + # Now create and return the state monitor + + + + sm = StateMonitor.StateMonitor(transUri, stateUri) + + # Register all state transitions to go to the UI state update: + # + for state in ['NotReady', 'Booting', 'Ready', 'Active']: + sm.register(state, reportTransition, None) + + return sm + +## +# createUi +# Create the user interface and connect to the buttonPush signal so button +# presses become transition requests. +# +# +# @return app,ui - The application object and the user interface object. + +def createUi(): + app = QtGui.QApplication(sys.argv) + w = cpanelWidget.ControlPanel() + w.setWindowTitle('Run Control') + w.show() + w.buttonPush.connect(onButtonPress) + return app,w + + +## +# eventLoop +# +# Interleave the event loop of the state monitor with the Qt4 eventloop. +# +# @param app - Qt4 application object. +# @param sm - State manager object. +# +def eventLoop(app, sm): + poller = sm.poller + + # TODO : Figure out how to make the control panel exit!! + + app.aboutToQuit.connect(exit) + ui.destroyed.connect(exit) + while True: + poller.poll(100) + app.processEvents(QtCore.QEventLoop.AllEvents, 100) + +## Stub +def reportTransition(sm, prior, current, cbarg): + ui.setState(current) + +def onButtonPress(transition): + smApi.requestTransition(str(transition)) + +#---------------------------------------------------------------------------- +# +# Main: +# * Parse the arguments +# * Connect to the state manager +# * Create the graphical user interface. +# * Run a hybrid event loop that swaps back and forth between the Qt and +# State manager's event loop so both can be satisfied. + +args = parseArgs() +smApi= connectStateManager(args) + +app,ui = createUi() +eventLoop (app, smApi) \ No newline at end of file Added: branches/nscldaq-11.0-development/utilities/cpanel/cpanel.xml =================================================================== --- branches/nscldaq-11.0-development/utilities/cpanel/cpanel.xml (rev 0) +++ branches/nscldaq-11.0-development/utilities/cpanel/cpanel.xml 2014-07-23 14:17:53 UTC (rev 3659) @@ -0,0 +1,279 @@ +<!-- chapter utilities --> + +<chapter id='chap.cpanel'> + <title id='chap.cpanel.title'>The state manager control panel</title> + <para> + The state manager control panel connects to the state manager. + See <link linkend='chap.statemgr' endterm='chap.statemgr.title' /> for + information about the state manager. For the manpage on the control + panel see + <link linkend='daq1-cpanel' endterm='daq1-cpanel-title' /> + </para> + <para> + The state manager provides a display that describes the current system + state as well as providing a context sensitive set of buttons that + allow you to request all legal state transitions for the current state. + Since the control panel gets state and transition publications, you can + start more than one panel and all panels will continue to be consistent + regardless of which panel initiates transitions. + </para> + <para> + The control panel (cpanel) needs to have a set of environment definitions made + to run correctly. To get those definitions made, source the + <filename>daqsetup.bash</filename> script from the top level directory of + the NSCLDAQ installation tree. + </para> + <para> + cpanel determines how to connect to the state manager by following + the procedure below: + </para> + <orderedlist> + <listitem> + <para> + If the <option>--state_uri</option> and <option>--transition-uri</option> + are both defined, they are used as the URI's for the state/transition + publication port and the transition request port respectively. + </para> + </listitem> + <listitem> + <para> + If the previous condition is not satisfied, cpanel will attempt + to obtain the values of the environment variables: + <literal>TRANSITION_SUBSCRIPTION_URI</literal> and + <literal>TRANSITION_REQUEST_URI</literal> and use + them as the state/transtion publication and transition + request port URI's respectively. Note that if cpanel + is run from the boot manager, the boot manager will have + defined these environment variables. Note as well, that + at least one control panel must not be run from the boot manager + because the system has to be booted somehow. + </para> + </listitem> + <listitem> + <para> + If the previous condition is not satisfied, cpanel will + attempt to connect to the user's state manager in + <literal>localhost</literal> using the state manager's + default service names to look up the ports needed. + </para> + </listitem> + </orderedlist> + <para> + Typically, in addition to the state manager, you will want + the boot manager running prior to asking the state manager + to make state transitions. Let's look at a typical sequence of + bringing up the experiment (booting it), starting and stopping data taking + and then shutting down the experiment. We're going to assum you've + just started the state and boot managers so that the system is in the + <literal>NotReady</literal> state. + </para> + <para> + The figure below shows what cpanel looks like when it is started: + <informalfigure> + + <mediaobject> + <imageobject> + <imagedata fileref='cpanel-notready.jpg' format='JPEG' /> + </imageobject> + </mediaobject> + </informalfigure> + At the right side of its window, the control panel will always display + the current state. The buttons appropriate to that state will + be at the left side of the window. The <guibutton>Boot</guibutton> button + will request that the state manager take a <literal>BOOT</literal> + transition to the <literal>Booting</literal> state. From there the + boot manager will take over, create rings and processes and initiate + a transition to the <literal>Ready</literal> state. + </para> + <para> + The figure below shows what cpanel looks like when the system is in the + <literal>Ready</literal> state. + <informalfigure> + <mediaobject> + <imageobject> + <imagedata fileref='cpanel-ready.jpg' format='JPEG' /> + </imageobject> + </mediaobject> + </informalfigure> + There are now two buttons: + <itemizedlist> + <listitem><para><guibutton>Begin</guibutton> requests a + <literal>BEGIN</literal> transition to the <literal>Active</literal> + state. Readout program that are hooked into the state manager + will use that to begin data taking. + </para> + </listitem> + <listitem><para><guibutton>Fail</guibutton> can be clicked if you + think something is wrong. It initiates a <literal>FAIL</literal> + transition that drops the system back into the <literal>NotReady</literal> + state. The boot manager will catch that transition and stop all + programs. + </para> + </listitem> + </itemizedlist> + </para> + <para> + If you click <guibutton>Begin</guibutton> to start the run, the + control panel window will look like this: + <informalfigure> + + <mediaobject> + <imageobject> + <imagedata fileref='cpanel-active.jpg' format='JPEG' /> + </imageobject> + </mediaobject> + </informalfigure> + <itemizedlist> + <listitem><para><guibutton>End</guibutton> when clicked will + request an <literal>END</literal> transition which will take the + system back to the <literal>Ready</literal> state. State aware + readout programs stop data taking when they observe that transition. + </para></listitem> + <listitem><para><guibutton>Fail</guibutton> when clicked requests + a <literal>FAIL</literal> transition to the <literal>NotReady</literal> + state. + </para> + </listitem> + </itemizedlist> + </para> + <para> + Once a run is ended, a new run can be started. The system can also be + shutdown by clicking the <guibutton>Fail</guibutton> button at any time. + </para> +</chapter> + +<!-- /chapter --> + +<!-- manpage 1daq --> + + <refentry id="daq1-cpanel"> + <refmeta> + <refentrytitle id='daq1-cpanel-title'>cpanel</refentrytitle> + <manvolnum>1daq</manvolnum> + </refmeta> + <refnamediv> + <refname>cpanel</refname> + <refpurpose>Control panel for state manager</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <cmdsynopsis> + <command> +$DAQBIN/cpanel <optional>options...</optional> + </command> + </cmdsynopsis> + + </refsynopsisdiv> + <refsect1> + <title>DESCRIPTION</title> + <para> + Control panel for the state and boot managers. Provides + a very simple GUI that can be used to control the state manager + and run it through it states via allowed transitions. + </para> + <para> + See <literal>OPTIONS</literal> below for the command line options. + Just a note first about how cpanel locates the state manager with + which it will interact. + <orderedlist> + <listitem> + <para> + If the <option>--state_uri</option> and <option>--transition-uri</option> + are both defined, they are used as the URI's for the state/transition + publication port and the transition request port respectively. + </para> + </listitem> + <listitem> + <para> + If the previous condition is not satisfied, cpanel will attempt + to obtain the values of the environment variables: + <literal>TRANSITION_SUBSCRIPTION_URI</literal> and + <literal>TRANSITION_REQUEST_URI</literal> and use + them as the state/transtion publication and transition + request port URI's respectively. Note that if cpanel + is run from the boot manager, the boot manager will have + defined these environment variables. Note as well, that + at least one control panel must not be run from the boot manager + because the system has to be booted somehow. + </para> + </listitem> + <listitem> + <para> + If the previous condition is not satisfied, cpanel will + attempt to connect to the user's state manager in + <literal>localhost</literal> using the state manager's + default service names to look up the ports needed. + </para> + </listitem> + </orderedlist> + </para> + </refsect1> + <refsect1> + <title> + OPTIONS + </title> + <variablelist> + <varlistentry> + <term><option>--help</option>, <option>-h</option></term> + <listitem> + <para> + Outputs some help text that describes how to run the + program. The program then exits without doing anything + else. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term><option>--version</option>, <option>-v</option></term> + <listitem> + <para> + Outputs the program version and then exits without doing + anything else. Note that if <option>--help</option> + is present on the command line, that takes + precedence. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term><option>--state-uri</option>, <option>-s</option></term> + <listitem> + <para> + The value of this option is a URI that specifies the + host/port on which the state manager is broadcasting + state and transition updates. Note that this + <emphasis>must</emphasis> be used in conjunction + with the <option>--transition-uri</option> option + or this option will be ignored. + URI's are of the form: + <literal>tcp://</literal><replaceable>hostname</replaceable><literal>:</literal><replaceable>portnum</replaceable> + For example: + <literal>--state-uri=tcp://localhost:1234</literal> + </para> + </listitem> + </varlistentry> + <varlistentry> + <term><option>--transition-uri</option>, <option>-t</option></term> + <listitem> + <para> + The value of this option is the URI that specifies the + host/port + on which the state manager + is accepting state transition requests. Note that this + <emphasis>must</emphasis> be used in conjunction + with the <option>--state-uri</option> option + or this option will be ignored. + </para> + <para> + See the <option>--state-uri</option> above for information + about the structure of URI's. + </para> + </listitem> + </varlistentry> + + </variablelist> + </refsect1> + + </refentry> + + +<!-- /manpage --> Added: branches/nscldaq-11.0-development/utilities/cpanel/cpanelWidget.py =================================================================== --- branches/nscldaq-11.0-development/utilities/cpanel/cpanelWidget.py (rev 0) +++ branches/nscldaq-11.0-development/utilities/cpanel/cpanelWidget.py 2014-07-23 14:17:53 UTC (rev 3659) @@ -0,0 +1,269 @@ +#!/usr/bin/env python + + + +# This software is Copyright by the Board of Trustees of Michigan +# State University (c) Copyright 2013. +# +# You may use this software under the terms of the GNU public license +# (GPL). The terms of this license are described at: +# +# http://www.gnu.org/licenses/gpl.txt +# +# Author: +# Ron Fox +# NSCL +# Michigan State University +# East Lansing, MI 48824-1321 + +## +# @file cpanelWidget.py +# @brief Run control panel widget. +# @author <fo...@ns...> + +from PyQt4 import QtGui, QtCore +import sys + +# +# This file contains a control panel widget that can be encapsulated +# into a larger application. +# This is because it: +# * Has an entry point to inform it of the current state. +# * Has an observer that can be informed of state change requests. +# +# A typical use is for the encapsulating app to be attached to the state +# manager. Its state transition/establishment callaback would inform the +# widget of the new (initial) state. It would establish an observer as well +# that would take transition requests and pass them on to the state manager. +# + + + +## +# @class ControlPanel +# +# Top level widget for the control panel. At present, this +# contains the following layout: +# +# +---------------------------------------------+ +# | +----------------------+ | +# | | State dependent | Current State | +# | | Buttons | | +# | +----------------------+ | +# +---------------------------------------------+ +# +# +class ControlPanel(QtGui.QWidget): + buttonPush = QtCore.pyqtSignal(QtCore.QString) + ## + # __init__ + # Construction initializes the base frame, + # creates an empty observer list and invokes _setup to + # create and layout the UI elements. + # + def __init__(self, parent=None): + super(ControlPanel, self).__init__(parent) + self._observers = list() + self._setup() + + ## + # _setup + # Layout the widget and attach events. + # + def _setup(self): + + # Use a grid layout for future expansion: + + self._layout = QtGui.QGridLayout() + self.setLayout(self._layout) + + # For now just put a frame up for the buttons because we don't know + # which buttons to display yet: + + self._buttonFrame = QtGui.QFrame(self) + self._buttonFrame.setLayout(QtGui.QGridLayout()) + self._buttonFrame.resize(400,400) + self._buttons = list() # No child widgets. + + self._layout.addWidget(self._buttonFrame, 0, 0) + + # Add the state label - initialized to + # unknown state. It's going to cell 0, 1 of the grid + + + self._stateLabel = QtGui.QLabel('>>Unknown<<', self) + self._layout.addWidget(self._stateLabel, 0, 1) + + + + + ## + # _setStateButtons + # * Destroys any old control buttons. + # * Creates the control buttons that are appropriate to this + # state. + # + # @param state - Name of state we just entered. + # + # @note we're going to be a bit dynamic here. We assume the + # existence of a method crate<State>Buttons where + # <State> is the value of the current state, and that that method + # actually knows how to create the buttons we need and attach + # them to events. + # + def _setStateButtons(self, state): + for b in self._buttons: + b.hide() + b.destroy() + del b + self._buttons = list() # should delete ad this is the only ref + creator = "self._create%sButtons()" % (state.upper()) + exec creator + + + ## + # _emitButtonPush + # Action handler for clicking a button.. the text of the button is + # retrieved, converted to upper case and used as the value of the + # buttonPush slot emit + # + def _emitButtonPush(self): + b = self.sender() + transition = str(b.text()) + transition = transition.upper() + self.buttonPush.emit(transition) + + #-------------------------------------------------------------------------- + # Button creation/layout for each state + + ## + # _createActionButton + # * Creates a button with the specified text. + # * Connects it to the _emitButtonPush method + # * adds it to the _buttons list + # + # @param text - Label on the button. + # @return button - The button widget. + # + def _createActionButton(self, text): + b = QtGui.QPushButton(text, self) + b.clicked.connect(self._emitButtonPush) + self._buttons.append(b) + return b + + ## + # _createNotReadyButtons + # In this state, the only legal operation is to Boot the system: + def _createNOTREADYButtons(self): + boot = self._createActionButton('Boot') + self._layout.addWidget(boot, 0, 0) + + ## + # _createBootingButtons + # In this state the user can fail the transition if they suspect + # a problem. + # + def _createBOOTINGButtons(self): + fail = self._createActionButton('Fail') + self._layout.addWidget(fail, 0, 0) + + ## + # _createReadyButtons + # Creates Begin/Fail buttons Top to bottom. + # + def _createREADYButtons(self): + begin = self._createActionButton('Begin') + fail = self._createActionButton('Fail') + self._layout.addWidget(begin, 0, 0) + self._layout.addWidget(fail, 1, 0) + pass + ## + # Active runs can be ended or failed if the user supsects a problem. + # + def _createACTIVEButtons(self): + end = self._createActionButton('End') + fail= self._createActionButton('Fail') + self._layout.addWidget(end, 0, 0) + self._layout.addWidget(fail, 1, 0) + + pass + + + + #-------------------------------------------------------------------------- + # public methods: + # + # + + ## + # setState + # Provides the widget with a new state value. + # + # @param state - the new state string + # + def setState(self, state): + self._stateLabel.setText(state) + self._setStateButtons(state) + + + ## + # addObserver + # Adds a callable which will be invoked when a button is clicked. + # The obsever gets passed the name of the transition that button would + # attempt to produce (e.g. BOOT, BEGIN, END). + # + # @param callable + # + # @note Observers are called in the order in which they get established. + # + def addObserver(self, callable): + self._observers.append(callable) + ## + # removeObserver + # Removes an existing observer from the list. This is a no-op if there + # is no observer like that in the list. + # + # @param callable - the observer to remove. + # + def removeObserver(self, callable): + try: + self._observers.remove(callable) + except ValueError: # Raised if callable not in list. + pass + + + +## +# If this is run as a main then pop up the widget, give it an initial +# state and run an observer that will simulate the statemachine. +# This supports testing. +# + +if __name__ == '__main__': + def fakeStateMachine(transition): + t = transition + if t == 'BOOT': + w.setState('Ready') # Skip booting since not button gets us out. + if t == 'BEGIN': + w.setState('Active') + if t == 'END': + w.setState('Ready') + if t == 'FAIL': + w.setState('NotReady') + + app = QtGui.QApplication(sys.argv) + w = ControlPanel() + w.setWindowTitle('testing control panel') + w.show() + + w.setState('NotReady') + + # Connect a fake state machine to the control panel: + + w.buttonPush.connect(fakeStateMachine) + + sys.exit(app.exec_()) + + + + Modified: branches/nscldaq-11.0-development/utilities/statemanager/Makefile.am =================================================================== --- branches/nscldaq-11.0-development/utilities/statemanager/Makefile.am 2014-07-22 18:35:38 UTC (rev 3658) +++ branches/nscldaq-11.0-development/utilities/statemanager/Makefile.am 2014-07-23 14:17:53 UTC (rev 3659) @@ -16,6 +16,7 @@ install-exec-local: $(mkinstalldirs) $(INSTDIR) + $(mkinstalldirs) @datarootdir@/html touch $(INSTDIR)/__init__.py for f in $(MAINS); do $(INSTALL_SCRIPT) @srcdir@/$$f @bindir@/`basename $$f .py`; done for f in $(PACKAGE_FILES); do $(INSTALL_SCRIPT) @srcdir@/$$f $(INSTDIR); done This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |