Low-Energy Muon (LEM) Experiment  0.5.1
lem_epics.c
Go to the documentation of this file.
1 /********************************************************************\
2 
3  Name: lem_epics.c
4  Created by: Stefan Ritt
5  Modified by: Thomas Prokscha
6 
7  Contents: Epics channel access device driver for PSI environment
8 
9  Modified: Avoid writing NAN's to Demand values; otherwise history
10  file is updated every second, even if no data changed.
11 
12 \********************************************************************/
13 
14 #include <stddef.h>
15 #include <stdio.h>
16 #include <time.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <epicsStdlib.h>
20 #include "cadef.h"
21 #include "dbDefs.h"
22 #include "epicsTime.h"
23 
24 #ifdef _MSC_VER /* collision between varargs and stdarg */
25 #undef va_arg
26 #undef va_start
27 #undef va_end
28 #endif
29 
30 #include "midas.h"
31 #include "msystem.h"
32 #include "lem_epics.h"
33 
34 /*---- globals -----------------------------------------------------*/
35 
36 #define CHN_NAME_LENGTH 32 /* length of channel names */
37 
38 #define CAGET_TIMEOUT 4.0
39 
40 #define MIN_TIMEOUT_COUNT 50 /* output frequency of timeout messages */
41 
42 static int timeout_count = 0;
43 
44 typedef struct {
45  char epics_gateway[256];
47 } CA_SETTINGS;
48 
49 typedef struct {
50  char name[CHN_NAME_LENGTH];
51  chid chan_id;
52  evid evt_id;
53  float value;
54 } CA_NODE;
55 
56 typedef struct {
62  char *channel_name;
66  DWORD flags;
67 } CA_INFO;
68 
69 struct ca_client_context *ca_context;
70 //
71 // Thomas Prokscha, PSI, 20-June-2019:
72 //
73 // We need to define our own exception handler to avoid aborts initiated by the default exception handler of the ca library
74 // The function below is a modified example of
75 // https://epics.anl.gov/base/R3-14/10-docs/CAref.html#ca_add_exception_event
76 // This exception handler is actually doing nothing except printing a message on screen.
77 // It is important to comment the line with th ca_signal() function since ca_signal() may cause a program abort.
79  struct exception_handler_args args)
80 {
81  char buf[512];
82  const char *pName;
83 
84  if ( args.chid ) {
85  pName = ca_name ( args.chid );
86  }
87  else{
88  pName = "?";
89  }
90  sprintf ( buf,
91  "%s - with request chan=%s op=%ld data type=%s count=%ld",
92  args.ctx, pName, args.op, dbr_type_to_text ( args.type ), args.count );
93 // ca_signal ( args.stat, buf ); //this may cause a program abort.
94 // cm_msg(MINFO,"ca_exception_handler","Msg. from exception handler: %s", buf);
95  return;
96 }
97 
98 
99 /*---- device driver routines --------------------------------------*/
100 
101 INT psi_epics_init(HNDLE hKey, void **pinfo, INT channels)
102 {
103  int status, i, size;
104  HNDLE hDB;
105  CA_INFO *info;
106  char str[256];
107 
108  /* allocate info structure */
109  info = calloc(1, sizeof(CA_INFO));
110  *pinfo = info;
111 
112  cm_get_experiment_database(&hDB, NULL);
113 
114  /* create settings record */
115  size = sizeof(info->ca_settings.epics_gateway);
116  strcpy(info->ca_settings.epics_gateway, "hipa-cagw01");
117  status = db_get_value(hDB, hKey, "EPICS Gateway", &info->ca_settings.epics_gateway, &size, TID_STRING, TRUE);
118  if (status != DB_SUCCESS)
119  return FE_ERR_ODB;
120  size = sizeof(info->ca_settings.gateway_port);
121  info->ca_settings.gateway_port = 5062;
122  status = db_get_value(hDB, hKey, "Gateway port", &info->ca_settings.gateway_port, &size, TID_INT, TRUE);
123  if (status != DB_SUCCESS)
124  return FE_ERR_ODB;
125 
126  /* PSI specific settings */
127  setenv("EPICS_CA_ADDR_LIST", info->ca_settings.epics_gateway, 1);
128  setenv("EPICS_CA_AUTO_ADDR_LIST", "NO", 1);
129  sprintf(str, "%d", info->ca_settings.gateway_port);
130  setenv("EPICS_CA_SERVER_PORT", str, 1);
131 
132  /* allocate arrays */
133  info->demand = calloc(channels, sizeof(CA_NODE));
134  info->measured = calloc(channels, sizeof(CA_NODE));
135  info->extra = calloc(channels, sizeof(CA_NODE));
136 
137  /* get channel names */
138  info->channel_name = calloc(channels, CHN_NAME_LENGTH);
139  for (i=0; i<channels; i++)
140  sprintf(info->channel_name + CHN_NAME_LENGTH * i, "<Empty>%d", i);
141  db_merge_data(hDB, hKey, "Channel name",
142  info->channel_name, CHN_NAME_LENGTH * channels, channels, TID_STRING);
143 
144  /* get demand strings */
145  info->demand_string = calloc(channels, CHN_NAME_LENGTH);
146  for (i=0; i<channels; i++)
147  sprintf(info->demand_string + CHN_NAME_LENGTH * i, "<Empty>%d", i);
148  db_merge_data(hDB, hKey, "Demand",
149  info->demand_string, CHN_NAME_LENGTH * channels, channels, TID_STRING);
150 
151  /* get measured strings */
152  info->measured_string = calloc(channels, CHN_NAME_LENGTH);
153  for (i=0; i<channels; i++)
154  sprintf(info->measured_string + CHN_NAME_LENGTH * i, "<Empty>%d", i);
155  db_merge_data(hDB, hKey, "Measured",
156  info->measured_string, CHN_NAME_LENGTH * channels, channels, TID_STRING);
157 
158  /* get device types */
159  info->device_type = calloc(channels, sizeof(INT));
160  for (i=0; i<channels; i++)
161  info->device_type[i] = DT_DEVICE;
162  db_merge_data(hDB, hKey, "Device type",
163  info->device_type, sizeof(INT) * channels, channels, TID_INT);
164 
165  /* create context for multi-thread handling */
166  SEVCHK(ca_context_create(ca_enable_preemptive_callback), "ca_context_create");
167  ca_context = ca_current_context();
168 
169  /* initialize driver */
170  status = ca_task_initialize();
171  if (!(status & CA_M_SUCCESS)) {
172  cm_msg(MERROR, "psi_epics_init", "Unable to initialize");
173  return FE_ERR_HW;
174  }
175 
176  /* search channels */
177  info->num_channels = channels;
178 
179  status = FE_SUCCESS;
180  for (i = 0; i < channels; i++) {
181 
182  /* connect to demand channel */
183  if (*(info->demand_string + i*CHN_NAME_LENGTH)) {
184  strlcpy(info->demand[i].name, info->channel_name + i*CHN_NAME_LENGTH, CHN_NAME_LENGTH);
185  strlcat(info->demand[i].name, info->demand_string + i*CHN_NAME_LENGTH, CHN_NAME_LENGTH);
186  status = ca_create_channel(info->demand[i].name, 0, 0, 0, &(info->demand[i].chan_id));
187  SEVCHK(status, "ca_create_channel");
188  if (ca_pend_io(5.0) == ECA_TIMEOUT) {
189  cm_msg(MERROR, "psi_epics_init", "Cannot connect to EPICS channel %s", info->demand[i].name);
190  status = FE_ERR_HW;
191  break;
192  }
193  } else {
194  info->demand[i].chan_id = NULL;
195  }
196 
197  /* connect to measured channel */
198  if (*(info->measured_string + i*CHN_NAME_LENGTH)) {
199  strlcpy(info->measured[i].name, info->channel_name + i*CHN_NAME_LENGTH, CHN_NAME_LENGTH);
200  strlcat(info->measured[i].name, info->measured_string + i*CHN_NAME_LENGTH, CHN_NAME_LENGTH);
201  status = ca_create_channel(info->measured[i].name, 0, 0, 0, &(info->measured[i].chan_id));
202  SEVCHK(status, "ca_create_channel");
203  if (ca_pend_io(5.0) == ECA_TIMEOUT) {
204  cm_msg(MERROR, "psi_epics_init", "Cannot connect to EPICS channel %s", info->measured[i].name);
205  status = FE_ERR_HW;
206  break;
207  }
208  } else {
209  info->measured[i].chan_id = NULL;
210  }
211 
212  /* connect command channel for separator */
213  if (info->device_type[i] == DT_SEPARATOR) {
214  strlcpy(info->extra[i].name, info->channel_name + i*CHN_NAME_LENGTH, CHN_NAME_LENGTH);
215  info->extra[i].name[strlen(info->extra[i].name)-1] = 0; // strip 'N' form SEP41VHVN
216  strlcat(info->extra[i].name, ":COM:2", CHN_NAME_LENGTH);
217  status = ca_create_channel(info->extra[i].name, 0, 0, 0, &(info->extra[i].chan_id));
218  SEVCHK(status, "ca_create_channel");
219  if (ca_pend_io(5.0) == ECA_TIMEOUT) {
220  cm_msg(MERROR, "psi_epics_init", "Cannot connect to EPICS channel %s", info->extra[i].name);
221  status = FE_ERR_HW;
222  break;
223  }
224  }
225  }
226 //
227 // Define now our own exception handler to avoid program aborts by the default epics ca library
228 //
229  status = ca_add_exception_event(ca_exception_handler, 0);
230  if (status != ECA_NORMAL)
231  cm_msg(MERROR, "psi_epics_init", "Error adding epics exception handler, status = %d", status);
232  else
233  cm_msg(MINFO, "psi_epics_init", "Successfully added epics exception handler.");
234 
235 
236  timeout_count = MIN_TIMEOUT_COUNT; //ensure that first epics timeout will be written to midas messages
237 
238  return FE_SUCCESS;
239 }
240 
241 /*----------------------------------------------------------------------------*/
242 
244 {
245  int i;
246 
247  if (info->demand) {
248  for (i = 0; i < info->num_channels; i++)
249  if (info->demand[i].chan_id)
250  ca_clear_channel(info->demand[i].chan_id);
251  free(info->demand);
252  }
253  if (info->measured) {
254  for (i = 0; i < info->num_channels; i++)
255  if (info->measured[i].chan_id)
256  ca_clear_channel(info->measured[i].chan_id);
257  free(info->measured);
258  }
259  if (info->extra) {
260  for (i = 0; i < info->num_channels; i++)
261  if (info->extra[i].chan_id)
262  ca_clear_channel(info->extra[i].chan_id);
263  free(info->extra);
264  }
265 
266  ca_task_exit();
267 
268  if (info->channel_name)
269  free(info->channel_name);
270 
271  if (info->demand_string)
272  free(info->demand_string);
273 
274  if (info->measured_string)
275  free(info->measured_string);
276 
277  if (info->device_type)
278  free(info->device_type);
279 
280  free(info);
281 
282  return FE_SUCCESS;
283 }
284 
285 /*----------------------------------------------------------------------------*/
286 
287 INT psi_epics_set(CA_INFO * info, INT channel, float value)
288 {
289  int status;
290  short int d;
291 
292  if (info->demand[channel].chan_id) {
293  if ((info->device_type[channel]) == DT_BEAMBLOCKER)
294  status = ca_put(DBR_STRING, info->demand[channel].chan_id, value == 1 ? "OPEN" : "CLOSE");
295 
296  else if ((info->device_type[channel]) == DT_SEPARATOR) {
297  status = ca_put(DBR_FLOAT, info->demand[channel].chan_id, &value);
298  if (status != CA_M_SUCCESS)
299  cm_msg(MERROR, "psi_epics_set", "Cannot write to EPICS channel %s", info->demand[channel].name);
300  d = 3; // HVSET command
301  status = ca_put(DBR_SHORT, info->extra[channel].chan_id, &d);
302 
303  } else
304  status = ca_put(DBR_FLOAT, info->demand[channel].chan_id, &value);
305 
306  if (status != CA_M_SUCCESS)
307  cm_msg(MERROR, "psi_epics_set", "Cannot write to EPICS channel %s", info->demand[channel].name);
308  }
309 
310  return FE_SUCCESS;
311 }
312 
313 /*----------------------------------------------------------------------------*/
314 
315 INT psi_epics_get(CA_INFO * info, INT channel, float *pvalue)
316 {
317  int status, d;
318 
319  if ((info->device_type[channel] & 0xFF) == DT_BEAMBLOCKER ||
320  (info->device_type[channel] & 0xFF) == DT_PSA) {
321 
322  status = ca_get(DBR_LONG, info->measured[channel].chan_id, &d);
323  SEVCHK(status, "ca_get");
324  if (ca_pend_io(CAGET_TIMEOUT) == ECA_TIMEOUT){
325  timeout_count++;
327  cm_msg(MINFO, "psi_epics_get", "Timeout on EPICS channel %s", info->measured[channel].name);
328  timeout_count = 0;
329  }
330  }
331  if ((info->device_type[channel] & 0xFF) == DT_BEAMBLOCKER) {
332  /* bit0: open, bit1: closed, bit2: psa ok */
333  if ((d & 3) == 0)
334  *pvalue = 0.5;
335  else if (d & 1)
336  *pvalue = 1;
337  else if (d & 2)
338  *pvalue = 0;
339  else
340  *pvalue = (float)ss_nan();
341  } else {
342  if (d & 4)
343  *pvalue = 1;
344  else
345  *pvalue = 0;
346  }
347 
348  } else {
349  status = ca_get(DBR_FLOAT, info->measured[channel].chan_id, pvalue);
350  SEVCHK(status, "ca_array_get");
351  if (ca_pend_io(CAGET_TIMEOUT) == ECA_TIMEOUT){
352  timeout_count++;
354  cm_msg(MINFO, "psi_epics_get", "Timeout on EPICS channel %s", info->measured[channel].name);
355  timeout_count = 0;
356  }
357  }
358  }
359 
360  //printf("%s %f\n", info->measured[channel].name, *pvalue);
361 
362  return FE_SUCCESS;
363 }
364 
365 /*----------------------------------------------------------------------------*/
366 
367 INT psi_epics_get_demand(CA_INFO * info, INT channel, float *pvalue)
368 {
369  int status;
370  char str[80];
371 
372  /* return -0.1111 and not NAN for readonly channels */
373  if (info->demand[channel].chan_id == NULL) {
374  *pvalue = ss_nan();
375 // *pvalue = -0.1111;
376  return FE_SUCCESS;
377  }
378 
379  if (info->device_type[channel] == DT_BEAMBLOCKER) {
380  status = ca_get(DBR_STRING, info->demand[channel].chan_id, str);
381  SEVCHK(status, "ca_get");
382  if (ca_pend_io(CAGET_TIMEOUT) == ECA_TIMEOUT){
383  timeout_count++;
385  cm_msg(MINFO, "psi_epics_get", "Timeout on EPICS channel %s", info->measured[channel].name);
386  timeout_count = 0;
387  }
388  }
389  *pvalue = (str[0] == 'O') ? 1 : 0;
390  } else {
391  status = ca_get(DBR_FLOAT, info->demand[channel].chan_id, pvalue);
392  SEVCHK(status, "ca_get");
393  if (ca_pend_io(CAGET_TIMEOUT) == ECA_TIMEOUT){
394  timeout_count++;
396  cm_msg(MINFO, "psi_epics_get", "Timeout on EPICS channel %s", info->measured[channel].name);
397  timeout_count = 0;
398  }
399  }
400  }
401 
402  return FE_SUCCESS;
403 }
404 
405 /*---- device driver entry point -----------------------------------*/
406 
407 INT lem_epics(INT cmd, ...)
408 {
409  va_list argptr;
410  HNDLE hKey;
411  INT channel, status;
412  DWORD flags;
413  float value, *pvalue;
414  char *name;
415  CA_INFO *info;
416 
417  va_start(argptr, cmd);
418  status = FE_SUCCESS;
419 
420  if (cmd == CMD_INIT) {
421  void *pinfo;
422 
423  hKey = va_arg(argptr, HNDLE);
424  pinfo = va_arg(argptr, void *);
425  channel = va_arg(argptr, INT);
426  flags = va_arg(argptr, DWORD);
427  status = psi_epics_init(hKey, pinfo, channel);
428  info = *(CA_INFO **) pinfo;
429  info->flags = flags;
430  } else {
431  info = va_arg(argptr, void *);
432 
433  /* only execute command if enabled */
434  switch (cmd) {
435  case CMD_INIT:
436  break;
437 
438  case CMD_EXIT:
439  status = psi_epics_exit(info);
440  break;
441 
442  case CMD_START:
443  SEVCHK(ca_attach_context(ca_context), "ca_attach_context");
444  break;
445 
446  case CMD_SET:
447  channel = va_arg(argptr, INT);
448  value = (float) va_arg(argptr, double);
449  status = psi_epics_set(info, channel, value);
450  break;
451 
452  case CMD_GET:
453  channel = va_arg(argptr, INT);
454  pvalue = va_arg(argptr, float *);
455  status = psi_epics_get(info, channel, pvalue);
456  break;
457 
458  case CMD_GET_DEMAND:
459  channel = va_arg(argptr, INT);
460  pvalue = va_arg(argptr, float *);
461  status = psi_epics_get_demand(info, channel, pvalue);
462  break;
463 
464  case CMD_GET_LABEL:
465  channel = va_arg(argptr, INT);
466  name = va_arg(argptr, char *);
467  strlcpy(name, info->channel_name + channel*CHN_NAME_LENGTH, CHN_NAME_LENGTH);
468  break;
469 
470  case CMD_GET_THRESHOLD:
471  channel = va_arg(argptr, INT);
472  pvalue = va_arg(argptr, float *);
473  *pvalue = 0.01;
474  break;
475 
476  default:
477  break;
478  }
479  }
480 
481  va_end(argptr);
482 
483  return status;
484 }
485 
486 /*------------------------------------------------------------------*/
CA_NODE * extra
Definition: lem_epics.c:60
CA_NODE * demand
Definition: lem_epics.c:58
chid chan_id
Definition: lem_epics.c:51
CA_SETTINGS ca_settings
Definition: lem_epics.c:57
INFO info
Definition: vme_fe.c:206
float value
Definition: lem_epics.c:53
int gateway_port
Definition: lem_epics.c:46
char * channel_name
Definition: lem_epics.c:62
#define DT_DEVICE
Definition: lem_epics.h:12
INT * device_type
Definition: lem_epics.c:65
DWORD flags
Definition: lem_epics.c:66
HNDLE hKey
Definition: write_summary.c:97
char name[CHN_NAME_LENGTH]
Definition: lem_epics.c:50
INT lem_epics(INT cmd,...)
Definition: lem_epics.c:407
#define DT_BEAMBLOCKER
Definition: lem_epics.h:13
HNDLE hDB
Definition: write_summary.c:97
#define CAGET_TIMEOUT
Definition: lem_epics.c:38
INT psi_epics_get(CA_INFO *info, INT channel, float *pvalue)
Definition: lem_epics.c:315
#define CHN_NAME_LENGTH
Definition: lem_epics.c:36
char * measured_string
Definition: lem_epics.c:64
char * demand_string
Definition: lem_epics.c:63
struct ca_client_context * ca_context
Definition: lem_epics.c:69
INT psi_epics_exit(CA_INFO *info)
Definition: lem_epics.c:243
INT psi_epics_init(HNDLE hKey, void **pinfo, INT channels)
Definition: lem_epics.c:101
#define DT_PSA
Definition: lem_epics.h:14
void ca_exception_handler(struct exception_handler_args args)
Definition: lem_epics.c:78
INT psi_epics_get_demand(CA_INFO *info, INT channel, float *pvalue)
Definition: lem_epics.c:367
#define MIN_TIMEOUT_COUNT
Definition: lem_epics.c:40
char epics_gateway[256]
Definition: lem_epics.c:45
static int timeout_count
Definition: lem_epics.c:42
CA_NODE * measured
Definition: lem_epics.c:59
evid evt_id
Definition: lem_epics.c:52
INT psi_epics_set(CA_INFO *info, INT channel, float value)
Definition: lem_epics.c:287
INT num_channels
Definition: lem_epics.c:61
#define DT_SEPARATOR
Definition: lem_epics.h:15