ID |
Date |
Author |
Topic |
Subject |
8
|
17 Jan 2004 |
Stefan Ritt | | Access to hardware in the MIDAS framework | > The result is strange because the get function is called all the time very
> fast (much faster then the 9 seconds as set in the equipment) and even
> before starting the run (I just put the flag RO_RUNNING).
This is on purpose. When the frontend is idle, it loops over the slow control
equipment as fast as possible. This way, you see changes in your hardware very
quickly. I see no reason to waste CPU cycles in the frontend when there are
better things to do like reading slow control equipment. Presume you have the
alarm system running, which turns off some equipment in case of an over
current. You better do this as quickly as possible, not wasting up to 9
seconds each time.
The 9 seconds you mention are for reading *EVENTS*. You have double
functionality: First, reading the slow control system, writing updated values
to the ODB, where someone else can display or evaluate them (in the alarm
system for example). Second, assemble events and sending them with the other
data to disk or tape. Only the second one gets controlled by RO_RUNNING and
the 9 seconds. You can see this by the updating event statists on your
frontend display, which increments only when running and then every 9 seconds. |
7
|
16 Jan 2004 |
Razvan Stefan Gornea | | Access to hardware in the MIDAS framework | The multimeter device is indeed to simple to use MIDAS but I am just trying
it as a learning experience. The DAQ system to develop involves VME crates
and general purpose I/O boards. The slow control part, especially accessing
the I/O boards seem to me more complex then the VME access. I want to
understand very well the "correct" way of using the MIDAS slow control
framework before starting the project.
I chose the second method and created a meterdev.c driver (essentially a
copy of the nulldev.c) where I changed the init. function and the get
function. I am not sending a "INIT ..." string because for this device it
is useless. In the get function I send a "D" and read my string. I changed
the frontend of the example to have a new driver list (in the first try I
eliminated the Output device but the ODB got corrupted, I guess the class
multi needs to have defined output channels). The output channel is linked
with nulldev and null (I guess this is like if they would not be present).
The result is strange because the get function is called all the time very
fast (much faster then the 9 seconds as set in the equipment) and even
before starting the run (I just put the flag RO_RUNNING).
Thanks for any help |
Attachment 1: frontend.c
|
//********************************************************************************************
//
// Name: frontend.c
// Created by: Razvan Stefan Gornea
//
// Contents: Slow Control frontend for a portable multimeter
//
// Log: 2004-01-15 14:22
// Writing down initial code.
//
//********************************************************************************************
#include <stdio.h>
#include "midas.h"
#include "class/multi.h"
#include "device/nulldev.h"
#include "meterdev.h"
#include "bus/null.h"
#include "bus/rs232.h"
// globals variables
// frontend name
char *frontend_name = "Slow Control";
// frontend file name
char *frontend_file_name = __FILE__;
// frontend loop
BOOL frontend_call_loop = FALSE;
// frontend display refresh
INT display_period = 1000;
// maximum event size in bytes
INT max_event_size = 10000;
// maximum event size for fragments in bytes
INT max_event_size_frag = 5*1024*1024;
// buffer size in bytes
INT event_buffer_size = 10*10000;
// equipment list
// device drivers
DEVICE_DRIVER multi_driver[] = {
{"Input", meterdev, 1, rs232, DF_INPUT},
{"Output", nulldev, 1, null, DF_OUTPUT},
{""}
};
// equipment list
EQUIPMENT equipment[] = {
{ "Multimeter", /* equipment name */
11, 0, /* event ID, trigger mask */
"SYSTEM", /* event buffer */
EQ_SLOW, /* equipment type */
0, /* event source */
"FIXED", /* format */
TRUE, /* enabled */
RO_RUNNING, /* read when running */
9000, /* read every 9 sec */
0, /* stop run after this event limit */
0, /* number of sub events */
1, /* log history every event */
"", "", "",
cd_multi_read, /* readout routine */
cd_multi, /* class driver main routine */
multi_driver, /* device driver list */
NULL, /* init string */
},
{ "" }
};
// routines
INT poll_event(INT source[], INT count, BOOL test) {return 1;};
INT interrupt_configure(INT cmd, INT source[], PTYPE adr) {return 1;};
// frontend initialization
INT frontend_init()
{
return CM_SUCCESS;
}
// frontend exit
INT frontend_exit()
{
return CM_SUCCESS;
}
// frontend loop
INT frontend_loop()
{
return CM_SUCCESS;
}
// begin of run
INT begin_of_run(INT run_number, char *error)
{
return CM_SUCCESS;
}
// end of run
INT end_of_run(INT run_number, char *error)
{
return CM_SUCCESS;
}
// pause run
INT pause_run(INT run_number, char *error)
{
return CM_SUCCESS;
}
// resume run
INT resume_run(INT run_number, char *error)
{
return CM_SUCCESS;
}
|
Attachment 2: meterdev.c
|
//********************************************************************************************
//
// Name: meter.c
// Created by: Razvan Stefan Gornea
//
// Contents: Device driver for a portable multimeter
//
// Log: 2004-01-15 14:22
// Writing down initial code.
//
//********************************************************************************************
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include "midas.h"
// globals variables
#define DEFAULT_TIMEOUT 10000 // 10 secondes
typedef struct {
int address;
} METERDEV_SETTINGS;
#define METERDEV_SETTINGS_STR "\
Address = INT : 1\n\
"
typedef struct {
METERDEV_SETTINGS meterdev_settings; // device settings
float *array; // data array
INT num_channels; // number of channels associated with this device
INT (*bd)(INT cmd, ...); // bus driver entry function
void *bd_info; // private settings and data related to the bus driver
HNDLE hkey; // ODB key for bd_info structure
} METERDEV_INFO;
// routines
// initialization function: access the ODB to creates the settings, initializes the variables
// and calls the initialization function of the bus driver
INT meterdev_init(HNDLE hkey, void **pinfo, INT channels, INT (*bd)(INT cmd, ...))
{
int status, size;
HNDLE hDB, hkeydd;
METERDEV_INFO *info;
// allocate info structure
info = calloc(1, sizeof(METERDEV_INFO));
*pinfo = info;
// get handle on current experiment ODB
cm_get_experiment_database(&hDB, NULL);
// create METERDEV settings record
status = db_create_record(hDB, hkey, "DD", METERDEV_SETTINGS_STR); // force the ODB structure to match the METERDEV_SETTINGS C structure
if (status != DB_SUCCESS) {
return FE_ERR_ODB;
}
db_find_key(hDB, hkey, "DD", &hkeydd); // get handle on the DD key in the ODB associated with a certain equipment and device as pointed by the "hkey" handle
size = sizeof(info->meterdev_settings); // get the size of the device settings structure
db_get_record(hDB, hkeydd, &info->meterdev_settings, &size, 0); // load the device settings for the ODB, i.e. ontent of DD key
// initialize the driver
info->num_channels = channels; // define the nmber of channels
info->array = calloc(channels, sizeof(float)); // allocate space for data
info->bd = bd; // set handle on bus driver
info->hkey = hkey; // set handle on the ODB key for the evice driver
if (!bd) { // if handle invalid return error
return FE_ERR_ODB;
}
// call the bus driver initialization routine
status = info->bd(CMD_INIT, info->hkey, &info->bd_info);
if (status != SUCCESS) {
return status;
}
// initialization of device, something like ...
//BD_PUTS("init");
// for this device no initialization string is needed ...
return FE_SUCCESS;
}
// decomission function: free memory allocation(s) and close device(s)
INT meterdev_exit(METERDEV_INFO *info)
{
// call EXIT function of bus driver, usually closes device
info->bd(CMD_EXIT, info->bd_info);
// free local variables
if (info->array) {
free(info->array);
}
free(info);
return FE_SUCCESS;
}
// set channel value
INT meterdev_set(METERDEV_INFO *info, INT channel, float value)
{
char str[80];
// set channel to a specific value, something like ...
sprintf(str, "SET %d %lf", channel, value);
BD_PUTS(str);
BD_GETS(str, sizeof(str), ">", DEFAULT_TIMEOUT);
// simulate writing by storing value in local array, has to be removed in a real driver
if (channel < info->num_channels) {
info->array[channel] = value;
}
return FE_SUCCESS;
}
// set all channels values
INT meterdev_set_all(METERDEV_INFO *info, INT channels, float *value)
{
int i;
char str[1000];
// put here some optimized form of setting all channels simultaneously like ...
strcpy(str, "SETALL ");
for (i=0 ; i<min(info->num_channels, channels) ; i++) {
sprintf(str+strlen(str), "%lf ", value[i]);
}
BD_PUTS(str);
BD_GETS(str, sizeof(str), ">", DEFAULT_TIMEOUT);
// simulate writing by storing values in local array
for (i=0 ; i<min(info->num_channels, channels) ; i++) {
info->array[i] = value[i];
}
return FE_SUCCESS;
}
// get channel value
INT meterdev_get(METERDEV_INFO *info, INT channel, float *pvalue)
{
int status, i;
char str[80];
char ascii_number[5];
// read value from channel, something like ...
//sprintf(str, "GET %d", channel);
sprintf(str, "D"); // request data
BD_PUTS(str);
status = BD_GETS(str, sizeof(str), "\n", DEFAULT_TIMEOUT); // read until getting a cariage return or exit on timeout
for (i = 0; i < 4; i++) { // transfer the number
ascii_number[i] = str[i+4];
}
ascii_number[4] = '\0'; // end of string
*pvalue = (float) atof(ascii_number); // convert from ASCII to float
// simulate reading by copying set data from local array
//if (channel < info->num_channels) {
// *pvalue = info->array[channel];
//}
//else {
// *pvalue = 0.f;
//}
return FE_SUCCESS;
}
// get all channels values
INT meterdev_get_all(METERDEV_INFO *info, INT channels, float *pvalue)
{
// int i;
/* put here some optimized form of reading all channels. If the deviced
does not support such a function, one can call nulldev_get() in a loop
strcpy(str, "GETALL");
BD_PUTS(str);
BD_GETS(str, sizeof(str), ">", DEFAULT_TIMEOUT);
for (i=0 ; i<min(info->num_channels, channels) ; i++)
pvalue[i] = atof(str+i*5); // extract individual values from reply
*/
/* simulate reading by copying set data from local array */
//for (i=0 ; i<min(info->num_channels, channels) ; i++)
// pvalue[i] = info->array[i];
return FE_SUCCESS;
}
// device driver entry point
INT meterdev(INT cmd, ...)
{
va_list argptr;
HNDLE hKey;
INT channel, status;
DWORD flags;
float value, *pvalue;
void *info, *bd;
va_start(argptr, cmd);
status = FE_SUCCESS;
switch (cmd) {
case CMD_INIT:
hKey = va_arg(argptr, HNDLE);
info = va_arg(argptr, void *);
channel = va_arg(argptr, INT);
flags = va_arg(argptr, DWORD);
bd = va_arg(argptr, void *);
status = meterdev_init(hKey, info, channel, bd);
break;
case CMD_EXIT:
info = va_arg(argptr, void *);
status = meterdev_exit(info);
break;
case CMD_SET:
info = va_arg(argptr, void *);
channel = va_arg(argptr, INT);
value = (float) va_arg(argptr, double); // floats are passed as double
status = meterdev_set(info, channel, value);
break;
case CMD_SET_ALL:
info = va_arg(argptr, void *);
channel = va_arg(argptr, INT);
pvalue = (float *) va_arg(argptr, float *);
status = meterdev_set_all(info, channel, pvalue);
break;
case CMD_GET:
info = va_arg(argptr, void *);
channel = va_arg(argptr, INT);
pvalue = va_arg(argptr, float*);
status = meterdev_get(info, channel, pvalue);
break;
case CMD_GET_ALL:
info = va_arg(argptr, void *);
channel = va_arg(argptr, INT);
pvalue = va_arg(argptr, float*);
status = meterdev_get_all(info, channel, pvalue);
break;
default:
break;
}
va_end(argptr);
return status;
}
|
6
|
14 Jan 2004 |
Stefan Ritt | | Access to hardware in the MIDAS framework | There is some information at
http://midas.triumf.ca/doc/html/Internal.html#Slow_Control_system
and at
http://midas/download/course/course_rt03.zip , file "part1.ppt", expecially
page 59 and page 62 "writing your own device driver".
So what you are missing for your application is a "device driver" for your
multimeter. The only function it has to implement is the function CMD_INIT
where you initialize the RS232 port, and the funciton CMD_GET, which sends
a "R" and reads the value. Now you have two options:
1) You implement RS232 calls directly in your device driver
You link against rs232.c and directly call rs232_init() at the inizialization,
then call rs232_write() and rs232_read() where you read your 14 ASCII
characters.
2) You call a "bus driver" in your device driver
This method makes the device driver independent of the underlying transport
interface. So if your next multimeter accepts the same "R" command over
Ethernet, you can just replace the RS232 bus driver by the TCPIP bus driver
without having to change your device driver. But I guess that method 2) is not
worth for such a simple device like your multimeter.
So take nulldev.c or dastemp.c as your starting point, put some RS232
initialization into the init routine and the communication via "R" into
the "get" routine. The slow control frontend, driven by mfe.c, should then
regularly read your multimeter and the value should appear in the ODB. Take
the examples/slowcont/frontend.c as an example, and adjust the multi_driver[]
list to use your new device driver (instead of the nulldev).
I would like to mention that the usage of midas only makes sense for some
experiemnts which require event based readout, using VME or CAMAC crates. If
your only task is to read out some devices which are called "slow control
equipment" in the midas language, then you might be better of with labview or
something. |
5
|
14 Jan 2004 |
Razvan Stefan Gornea | | Access to hardware in the MIDAS framework | I am just starting to explore MIDAS, i.e. reading the manual and trying
some examples. For the moment I would like to make a simple frontend that
access a portable multimeter through RS-232 port. I think this could help
me understand how to access hardare inside MIDAS framework. Initially I've
started from the MiniFE.c example and tried to initialize the serial port
on run start transition and build a readout loop in the main function. I
know that this is not a full frontend but I was just interested in getting
some experience with the drivers available in the distribution, in this
case RS-232. The portable multimeter is very simple in principle, one just
has to configure the port settings and then send character 'R' and read 14
ASCII characters from the device. Unfortunately I could not understand how
to invoke the driver services so I changed and started again with the
slowcont/frontend.c example. From this example and after reading the "Slow
Control System" section in the MIDAS manual I think that all I need to do
is to define my own equipment structure based on the multi.c class driver
with a single input channel (and replace the null driver with the RS-232).
Here I got stuck. I see from the code source that there is a relationship
between drivers at all levels (even bus) and the ODB but I don't yet fully
understand how they work. Actually for a couple of days now I am in a loop
going from class to device to bus and then back again to class drivers
trying to see how to create my own device driver and especially how to call
the bus driver. It could be that the framework is invoking the drivers and
the user just has to configure things ... up to now I didn't dare to look
at the mfe.c.
Is there a more detailed documentation about slow control and drivers then
the MIDAS manual? What is the data flow through the three layers system for
drivers? What is the role of the framework and what is left to the user
choice?
Thanks |
4
|
18 Dec 2003 |
Stefan Ritt | | Alarm on no ping? | > I want midas alarms to go off when I cannot ping arbitrary remote hosts. Is
> there is easy/preferred way to do this? K.O.
There are "internal alarms" with type AT_EVALUATED. Just find a program
where you can put some code which gets periodically executed (like the idle
loop in the frontend), and so something like:
DWORD last = 0;
if (ss_time() > last+60)
{
last = ss_time();
/* do a ping via socket(), bind() and connect() */
...
if (status != CM_SUCCESS)
al_trigger_alarm("XYZ Ping", str, "Warning",
"Host is dead", AT_INTERNAL);
}
Pierre does the same thing in lazylogger.c, just have a look. I don't know
how to do a ping correctly in C, I guess you have to send an UDP packet
somewhere, but I never did it. If you find it out, please post it.
|
3
|
27 Aug 2003 |
Pierre-André Amaudruz | | Operation under 1.9.3 with the analyzer | 1) Prior upgrading midas to 1.9.3, make sure you've saved your ODB in ASCII
format using "odbedit> save my_odb.odb", as the internal structure is
incompatible with previous version. You will be able to restore it once
the new odb is up using "odbedit> load my_odb.odb".
2) since version 1.9.2, the analyzer supports ROOT and PAW packages.
The general Midas makefile build the analyzer core system mana.c
differently depending on presence of the environment variable $ROOTSYS.
In the case $ROOTSYS is not defined, the Makefile will create:
~/os/lib/mana.o, build for NO HBOOK calls.
~/os/lib/hmana.o, build with HBOOK calls for PAW analyzer
(requires /cern/pro/lib to be present).
In the case $ROOTSYS is defined and pointing to a valid root directory:
~/os/lib/mana.o, build for NO HBOOK calls.
~/os/lib/rmana.o, build for ROOT analyzer.
3) Since 1.9.2, the ~/examples/experiment contains the ROOT
analyzer example instead of HBOOK. The local Makefile uses the source
examples and the ~/os/lib/rmana.o for building the final user
application.
The previous HBOOK(PAW) analyzer has been moved into ~examples/hbookexpt
directory. The analyzer is build using the ~/os/lib/hmana.o
4) A new application "rmidas" is available when the system is build with
ROOT support. This application is an initial "pure" ROOT GUI implementing
TSocket for remote ROOT histogram display.
Once a ONLINE ROOT analyzer is up and running, by invoking "rmidas"
you will be prompt for a host name. Enter the node name hosting the
analyzer. You will be presented with a list of histogram which can
be display in a ROOT frame environment (see attachment).
5) The support of ROOT is also available for the logger by changing
the data format and the destination file name in the ODB structure.
This option will save on file the Midas banks converted into ROOT Tree.
This file can be opened with ROOT (see attachment).
------- ODB structure of /Logger/Channels/0/Settings
[local:midas:R]Settings>ls
Active y
Type Disk
Filename run%05d.root <<<<<<<<< new extension
Format ROOT <<<<<<<<< new format
Compression 0
ODB dump y
Log messages 0
Buffer SYSTEM
Event ID -1
Trigger mask -1
Event limit 0
Byte limit 0
Tape capacity 0
Subdir format
Current filename run00211.root
-------
. |
Attachment 1: rmidas-mlogger_root_file.jpg
|
|
2
|
11 Aug 2003 |
Konstantin Olchanski | | Alarm on no ping? | I want midas alarms to go off when I cannot ping arbitrary remote hosts. Is
there is easy/preferred way to do this? K.O. |
1
|
06 Jun 2003 |
Pierre-André Amaudruz | | Welcome | Dear Midas users,
As you certainly aware, ELOG (Electronic Logbook) has been written
by Stefan Ritt and its functionality is part of the Midas package too.
This web site using Elog is replacing the W-Agora Forum previously setup.
You will need to register to this forum in order to gain Write access and
possible Email notification.
We would like to encourage you to post your questions or comments at
this Midas Elog site instead of using private Email to the authors as your
remarks are surely of interest to the other users too.
|
|