Example #1
0
static void printChidInfo(chid chid, char *message)
{
    printf("pv: %s  type(%d) nelements(%ld) host(%s)", ca_name(chid),ca_field_type(chid),ca_element_count(chid), ca_host_name(chid));
    printf(" read(%d) write(%d) state(%d), message:%s\n", ca_read_access(chid),ca_write_access(chid),ca_state(chid), message);
};
Example #2
0
PyObject *pv_getter_pvval(pvobject * self, void *closure)
{

	if (self->chanId == NULL || ca_state(self->chanId) != cs_conn
	    || self->buff == NULL) {
		PYCA_ERR("pvval: indef");
	}

	int i;
	dbr_plaintype data;
	int dim = ca_element_count(self->chanId);
	chtype type = xxx_ca_field_type(self->chanId);
	PyObject *returnvalue = NULL;

	/* build the returned tuple */
	if (dim > 1)
		returnvalue = PyTuple_New(dim);

	switch (type) {
	case DBR_STRING:
		if (dim > 1)
			for (i = 0; i < dim; i++) {
				ca_module_utilsextract(self->buff,
						       xxx_ca_field_type
						       (self->chanId), i,
						       &data);
				PyTuple_SetItem(returnvalue, i,
						PyString_FromString(data.s));
		} else
			returnvalue =
			    PyString_FromString(((struct dbr_time_string *)
						 self->buff)->value);
		break;
	case DBR_DOUBLE:
		if (dim > 1)
			for (i = 0; i < dim; i++) {
				ca_module_utilsextract(self->buff,
						       xxx_ca_field_type
						       (self->chanId), i,
						       &data);
				PyTuple_SetItem(returnvalue, i,
						PyFloat_FromDouble(data.d));
		} else
			returnvalue =
			    PyFloat_FromDouble(((struct dbr_time_double *)
						self->buff)->value);
		break;
	case DBR_LONG:
		if (dim > 1)
			for (i = 0; i < dim; i++) {
				ca_module_utilsextract(self->buff,
						       xxx_ca_field_type
						       (self->chanId), i,
						       &data);
				PyTuple_SetItem(returnvalue, i,
						PyLong_FromLong(data.l));
		} else
			returnvalue = PyLong_FromLong(((struct dbr_time_long *)
						       self->buff)->value);
		break;
	case DBR_SHORT:
		if (dim > 1)
			for (i = 0; i < dim; i++) {
				ca_module_utilsextract(self->buff,
						       xxx_ca_field_type
						       (self->chanId), i,
						       &data);
				PyTuple_SetItem(returnvalue, i,
						PyInt_FromLong(data.i));
		} else
			returnvalue = PyInt_FromLong(((struct dbr_time_short *)
						      self->buff)->value);
		break;
	case DBR_CHAR:
		if (dim > 1)
			for (i = 0; i < dim; i++) {
				ca_module_utilsextract(self->buff,
						       xxx_ca_field_type
						       (self->chanId), i,
						       &data);
				PyTuple_SetItem(returnvalue, i,
						PyInt_FromLong(data.c));
		} else
			returnvalue = PyInt_FromLong(((struct dbr_time_char *)
						      self->buff)->value);
		break;
	case DBR_ENUM:
		if (dim > 1)
			for (i = 0; i < dim; i++) {
				ca_module_utilsextract(self->buff,
						       xxx_ca_field_type
						       (self->chanId), i,
						       &data);
				PyTuple_SetItem(returnvalue, i,
						PyInt_FromLong(data.e));
		} else
			returnvalue = PyInt_FromLong(((struct dbr_time_enum *)
						      self->buff)->value);
		break;
	case DBR_FLOAT:
		if (dim > 1)
			for (i = 0; i < dim; i++) {
				ca_module_utilsextract(self->buff,
						       xxx_ca_field_type
						       (self->chanId), i,
						       &data);
				PyTuple_SetItem(returnvalue, i,
						PyFloat_FromDouble(data.f));
		} else
			returnvalue =
			    PyFloat_FromDouble(((struct dbr_time_float *)
						self->buff)->value);
		break;
	default:
		PYCA_ERR("pvval: invalid");
		break;
	}

	return returnvalue;
}
Example #3
0
int caget (pv *pvs, int nPvs, OutputT format,
           chtype dbrType, unsigned long reqElems)
{
    unsigned int i;
    int n, result;

    for (n = 0; n < nPvs; n++) {

                                /* Set up pvs structure */
                                /* -------------------- */

                                /* Get natural type and array count */
        pvs[n].nElems  = ca_element_count(pvs[n].chid);
        pvs[n].dbfType = ca_field_type(pvs[n].chid);
        pvs[n].dbrType = dbrType;

                                /* Set up value structures */
        pvs[n].dbrType = dbf_type_to_DBR_TIME(pvs[n].dbfType); /* Use native type */
        if (dbr_type_is_ENUM(pvs[n].dbrType))             /* Enums honour -n option */
        {
            if (enumAsNr) pvs[n].dbrType = DBR_TIME_INT;
            else          pvs[n].dbrType = DBR_TIME_STRING;
        }

        if (reqElems == 0 || pvs[n].nElems < reqElems)    /* Adjust array count */
            pvs[n].reqElems = pvs[n].nElems;
        else
            pvs[n].reqElems = reqElems;

                                /* Issue CA request */
                                /* ---------------- */

        if (ca_state(pvs[n].chid) == cs_conn)
        {
            nConn++;
            pvs[n].onceConnected = 1;
                                   /* Allocate value structure */
            pvs[n].value = calloc(1, dbr_size_n(pvs[n].dbrType, pvs[n].reqElems));
            if(!pvs[n].value){
                fprintf(stderr,"Allocation failed\n");
                exit(1);
            }
            result = ca_array_get(pvs[n].dbrType,
                                  pvs[n].reqElems,
                                  pvs[n].chid,
                                  pvs[n].value);
            pvs[n].status = result;
        } else {
            pvs[n].status = ECA_DISCONN;
        }
    }
    if (!nConn) return 1;              /* No connection? We're done. */

                                /* Wait for completion */
                                /* ------------------- */

    result = ca_pend_io(caTimeout);
    if (result == ECA_TIMEOUT)
        fprintf(stderr, "Read operation timed out: PV data was not read.\n");

                                /* Print the data */
                                /* -------------- */

    for (n = 0; n < nPvs; n++) {

        switch (format) {
        case plain:             /* Emulate old caput behaviour */
            if (pvs[n].reqElems <= 1 && fieldSeparator == ' ') printf("%-30s", pvs[n].name);
            else                                               printf("%s", pvs[n].name);
            printf("%c", fieldSeparator);
        case terse:
            if (pvs[n].status == ECA_DISCONN)
                printf("*** not connected\n");
            else if (pvs[n].status == ECA_NORDACCESS)
                printf("*** no read access\n");
            else if (pvs[n].status != ECA_NORMAL)
                printf("*** CA error %s\n", ca_message(pvs[n].status));
            else if (pvs[n].value == 0)
                printf("*** no data available (timeout)\n");
            else
            {
                if (charArrAsStr && dbr_type_is_CHAR(pvs[n].dbrType) && (reqElems || pvs[n].reqElems > 1)) {
                    dbr_char_t *s = (dbr_char_t*) dbr_value_ptr(pvs[n].value, pvs[n].dbrType);
                    int dlen = epicsStrnEscapedFromRawSize((char*)s, strlen((char*)s));
                    char *d = calloc(dlen+1, sizeof(char));
                    if(!d){
                        fprintf(stderr,"Allocation failed\n");
                        exit(1);
                    }
                    epicsStrnEscapedFromRaw(d, dlen+1, (char*)s, strlen((char*)s));
                    printf("%s", d);
                    free(d);
                } else {
                    if (reqElems || pvs[n].nElems > 1) printf("%lu%c", pvs[n].reqElems, fieldSeparator);
                    for (i=0; i<pvs[n].reqElems; ++i) {
                        if (i) printf ("%c", fieldSeparator);
                        printf("%s", val2str(pvs[n].value, pvs[n].dbrType, i));
                    }
                }
                printf("\n");
            }
            break;
        case all:
            print_time_val_sts(&pvs[n], reqElems);
            break;
        default :
            break;
        }
    }
    return 0;
}
Example #4
0
static void dbCaTask(void *arg)
{
    taskwdInsert(0, NULL, NULL);
    SEVCHK(ca_context_create(ca_enable_preemptive_callback),
        "dbCaTask calling ca_context_create");
    dbCaClientContext = ca_current_context ();
    SEVCHK(ca_add_exception_event(exceptionCallback,NULL),
        "ca_add_exception_event");
    epicsEventSignal(startStopEvent);

    /* channel access event loop */
    while (TRUE){
        do {
            epicsEventMustWait(workListEvent);
        } while (dbCaCtl == ctlPause);
        while (TRUE) { /* process all requests in workList*/
            caLink *pca;
            short  link_action;
            int    status;

            epicsMutexMustLock(workListLock);
            if (!(pca = (caLink *)ellGet(&workList))){  /* Take off list head */
                epicsMutexUnlock(workListLock);
                if (dbCaCtl == ctlExit) goto shutdown;
                break; /* workList is empty */
            }
            link_action = pca->link_action;
            pca->link_action = 0;
            if (link_action & CA_CLEAR_CHANNEL) --removesOutstanding;
            epicsMutexUnlock(workListLock);         /* Give back immediately */
            if (link_action & CA_CLEAR_CHANNEL) {   /* This must be first */
                dbCaLinkFree(pca);
                /* No alarm is raised. Since link is changing so what? */
                continue; /* No other link_action makes sense */
            }
            if (link_action & CA_CONNECT) {
                status = ca_create_channel(
                      pca->pvname,connectionCallback,(void *)pca,
                      CA_PRIORITY_DB_LINKS, &(pca->chid));
                if (status != ECA_NORMAL) {
                    errlogPrintf("dbCaTask ca_create_channel %s\n",
                        ca_message(status));
                    printLinks(pca);
                    continue;
                }
                dbca_chan_count++;
                status = ca_replace_access_rights_event(pca->chid,
                    accessRightsCallback);
                if (status != ECA_NORMAL) {
                    errlogPrintf("dbCaTask replace_access_rights_event %s\n",
                        ca_message(status));
                    printLinks(pca);
                }
                continue; /*Other options must wait until connect*/
            }
            if (ca_state(pca->chid) != cs_conn) continue;
            if (link_action & CA_WRITE_NATIVE) {
                assert(pca->pputNative);
                if (pca->putType == CA_PUT) {
                    status = ca_array_put(
                        pca->dbrType, pca->nelements,
                        pca->chid, pca->pputNative);
                } else if (pca->putType==CA_PUT_CALLBACK) {
                    status = ca_array_put_callback(
                        pca->dbrType, pca->nelements,
                        pca->chid, pca->pputNative,
                        putCallback, pca);
                } else {
                    status = ECA_PUTFAIL;
                }
                if (status != ECA_NORMAL) {
                    errlogPrintf("dbCaTask ca_array_put %s\n",
                        ca_message(status));
                    printLinks(pca);
                }
                epicsMutexMustLock(pca->lock);
                if (status == ECA_NORMAL) pca->newOutNative = FALSE;
                epicsMutexUnlock(pca->lock);
            }
            if (link_action & CA_WRITE_STRING) {
                assert(pca->pputString);
                if (pca->putType == CA_PUT) {
                    status = ca_array_put(
                        DBR_STRING, 1,
                        pca->chid, pca->pputString);
                } else if (pca->putType==CA_PUT_CALLBACK) {
                    status = ca_array_put_callback(
                        DBR_STRING, 1,
                        pca->chid, pca->pputString,
                        putCallback, pca);
                } else {
                    status = ECA_PUTFAIL;
                }
                if (status != ECA_NORMAL) {
                    errlogPrintf("dbCaTask ca_array_put %s\n",
                        ca_message(status));
                    printLinks(pca);
                }
                epicsMutexMustLock(pca->lock);
                if (status == ECA_NORMAL) pca->newOutString = FALSE;
                epicsMutexUnlock(pca->lock);
            }
            /*CA_GET_ATTRIBUTES before CA_MONITOR so that attributes available
             * before the first monitor callback                              */
            if (link_action & CA_GET_ATTRIBUTES) {
                status = ca_get_callback(DBR_CTRL_DOUBLE,
                    pca->chid, getAttribEventCallback, pca);
                if (status != ECA_NORMAL) {
                    errlogPrintf("dbCaTask ca_get_callback %s\n",
                        ca_message(status));
                    printLinks(pca);
                }
            }
            if (link_action & CA_MONITOR_NATIVE) {
                size_t element_size;
    
                element_size = dbr_value_size[ca_field_type(pca->chid)];
                epicsMutexMustLock(pca->lock);
                pca->pgetNative = dbCalloc(pca->nelements, element_size);
                epicsMutexUnlock(pca->lock);
                status = ca_add_array_event(
                    ca_field_type(pca->chid)+DBR_TIME_STRING,
                    ca_element_count(pca->chid),
                    pca->chid, eventCallback, pca, 0.0, 0.0, 0.0, 0);
                if (status != ECA_NORMAL) {
                    errlogPrintf("dbCaTask ca_add_array_event %s\n",
                        ca_message(status));
                    printLinks(pca);
                }
            }
            if (link_action & CA_MONITOR_STRING) {
                epicsMutexMustLock(pca->lock);
                pca->pgetString = dbCalloc(1, MAX_STRING_SIZE);
                epicsMutexUnlock(pca->lock);
                status = ca_add_array_event(DBR_TIME_STRING, 1,
                    pca->chid, eventCallback, pca, 0.0, 0.0, 0.0, 0);
                if (status != ECA_NORMAL) {
                    errlogPrintf("dbCaTask ca_add_array_event %s\n",
                        ca_message(status));
                    printLinks(pca);
                }
            }
        }
        SEVCHK(ca_flush_io(), "dbCaTask");
    }
shutdown:
    taskwdRemove(0);
    if (dbca_chan_count == 0)
        ca_context_destroy();
    else
        fprintf(stderr, "dbCa: chan_count = %d at shutdown\n", dbca_chan_count);
    epicsEventSignal(startStopEvent);
}
Example #5
0
static void connectionCallback(struct connection_handler_args arg)
{
    caLink *pca;
    short link_action = 0;
    struct link *plink;

    pca = ca_puser(arg.chid);
    assert(pca);
    epicsMutexMustLock(pca->lock);
    plink = pca->plink;
    if (!plink) goto done;
    pca->isConnected = (ca_state(arg.chid) == cs_conn);
    if (!pca->isConnected) {
        struct pv_link *ppv_link = &plink->value.pv_link;
        dbCommon *precord = ppv_link->precord;

        pca->nDisconnect++;
        if (precord &&
            ((ppv_link->pvlMask & pvlOptCP) ||
             ((ppv_link->pvlMask & pvlOptCPP) && precord->scan == 0)))
            scanOnce(precord);
        goto done;
    }
    pca->hasReadAccess = ca_read_access(arg.chid);
    pca->hasWriteAccess = ca_write_access(arg.chid);

    if (pca->gotFirstConnection) {
        if (pca->nelements != ca_element_count(arg.chid) ||
            pca->dbrType != ca_field_type(arg.chid)) {
            /* BUG: We have no way to clear any old subscription with the
             *      originally chosen data type/size.  That will continue
             *      to send us data and will result in an assert() fail.
             */
            /* Let next dbCaGetLink and/or dbCaPutLink determine options */
            plink->value.pv_link.pvlMask &=
                ~(pvlOptInpNative | pvlOptInpString |
                  pvlOptOutNative | pvlOptOutString);

            pca->gotInNative  = 0;
            pca->gotOutNative = 0;
            pca->gotInString  = 0;
            pca->gotOutString = 0;
            free(pca->pgetNative); pca->pgetNative = 0;
            free(pca->pgetString); pca->pgetString = 0;
            free(pca->pputNative); pca->pputNative = 0;
            free(pca->pputString); pca->pputString = 0;
        }
    }
    pca->gotFirstConnection = TRUE;
    pca->nelements = ca_element_count(arg.chid);
    pca->dbrType = ca_field_type(arg.chid);
    if ((plink->value.pv_link.pvlMask & pvlOptInpNative) && !pca->pgetNative) {
        link_action |= CA_MONITOR_NATIVE;
    }
    if ((plink->value.pv_link.pvlMask & pvlOptInpString) && !pca->pgetString) {
        link_action |= CA_MONITOR_STRING;
    }
    if ((plink->value.pv_link.pvlMask & pvlOptOutNative) && pca->gotOutNative) {
        link_action |= CA_WRITE_NATIVE;
    }
    if ((plink->value.pv_link.pvlMask & pvlOptOutString) && pca->gotOutString) {
        link_action |= CA_WRITE_STRING;
    }
    pca->gotAttributes = 0;
    if (pca->dbrType != DBR_STRING) {
        link_action |= CA_GET_ATTRIBUTES;
    }
done:
    if (link_action) addAction(pca, link_action);
    epicsMutexUnlock(pca->lock);
}
static void medmConnectEventCb(struct connection_handler_args args) {
    int status;
    Channel *pCh = (Channel *)ca_puser(args.chid);
    int count;

  /* Increment the event counter */
    caTask.caEventCount++;

  /* Check for valid values */
    if(globalDisplayListTraversalMode != DL_EXECUTE) return;
    if(!pCh || !pCh->chid || !pCh->pr) {
	medmPostMsg(0,"medmConnectEventCb: Invalid channel information\n");
	return;
    }

  /* Do a get every time a channel is connected or reconnected and has
   * read access.  The get will cause the graphical info callback to
   * be called */
    if(args.op == CA_OP_CONN_UP && ca_read_access(pCh->chid)) {
	status = ca_array_get_callback(
	  dbf_type_to_DBR_CTRL(ca_field_type(args.chid)),
	  1, args.chid, medmUpdateGraphicalInfoCb, NULL);
	if(status != ECA_NORMAL) {
	    medmPostMsg(0,"medmConnectEventCb: "
	      "ca_array_get_callback [%s]:\n %s\n",
	      ca_name(pCh->chid)?ca_name(pCh->chid):"Unknown",
	      ca_message(status));
#if DEBUG_CONNECTION
# if 0
	    system("netstat | grep iocacis");
# endif
	    print("  pCh->chid %s args.chid\n",
	      pCh->chid == args.chid?"==":"!=");
	    print(
	      "  Channel Name: %s\n"
	      "  State: %s\n"
	      "  Native Type: %s\n"
	      "  Native Count: %hu\n"
	      "  Access: %s%s\n"
	      "  IOC: %s\n",
	      args.chid?ca_name(args.chid):"Unavailable",
	      args.chid?ca_state(args.chid) == cs_never_conn?"Never":
	      ca_state(args.chid) == cs_prev_conn?"Prev":
	      ca_state(args.chid) == cs_conn?"Conn":
	      ca_state(args.chid) == cs_closed?"Closed":"Unknown":"Unavailable",
	      args.chid?dbf_type_to_text(ca_field_type(args.chid)):"Unavailable",
	      args.chid?ca_element_count(args.chid):0,
	      args.chid?(ca_read_access(args.chid)?"R":"None"):"Unavailable",
	      args.chid?(ca_write_access(args.chid)?"W":""):"",
	      args.chid?ca_host_name(args.chid):"Unavailable");
#endif
	}
    }

  /* Handle four cases: connected or not and previously connected or not */
    if(args.op == CA_OP_CONN_UP) {
      /* Connected */
	if(pCh->previouslyConnected == False) {
	  /* Connected and not previously connected */
	  /* Set these first so they will be right for the updateValueCb */
	    pCh->pr->elementCount = ca_element_count(pCh->chid);
	    pCh->pr->dataType = ca_field_type(args.chid);
	    pCh->pr->connected = True;
	    caTask.channelConnected++;
	  /* Add the access-rights-change callback and the
	     significant-change (value, alarm or severity)
	     callback. Don't call the updateValueCb because
	     ca_replace_access_rights_event will call it. */
	    status = ca_replace_access_rights_event(
	      pCh->chid,medmReplaceAccessRightsEventCb);
	    if(status != ECA_NORMAL) {
		medmPostMsg(0,"medmConnectEventCb: "
		  "ca_replace_access_rights_event [%s]: %s\n",
		  ca_name(pCh->chid)?ca_name(pCh->chid):"Unknown",
		  ca_message(status));
	    }
	    if(pCh->pr->currentCount == -2) {
                count=0;
            } else {
                count=ca_element_count(pCh->chid);
            }
#ifdef __USING_TIME_STAMP__
	    status = ca_add_array_event(
	      dbf_type_to_DBR_TIME(ca_field_type(pCh->chid)),
	      count, pCh->chid,
	      medmUpdateChannelCb, pCh, 0.0,0.0,0.0, &(pCh->evid));
#else
	    status = ca_add_array_event(
	      dbf_type_to_DBR_STS(ca_field_type(pCh->chid)),
	      count, pCh->chid,
	      medmUpdateChannelCb, pCh, 0.0,0.0,0.0, &(pCh->evid));
#endif
	    if(status != ECA_NORMAL) {
	      /* Set the pointer to NULL in case CA didn't.  We don't
                 want to use it or clear it later. */
#if DEBUG_CONNECTION
		if(!pCh->evid) {
		    print("medmConnectEventCb: ca_add_array_event: \n"
		      "  status[%d] != ECA_NORMAL and pCh->evid != NULL\n",
		      status);
		}
#endif
		pCh->evid = NULL;
		medmPostMsg(0,"medmConnectEventCb: "
		  "ca_add_array_event [%s]:\n %s\n",
		  ca_name(pCh->chid)?ca_name(pCh->chid):"Unknown",
		  ca_message(status));
#if DEBUG_CONNECTION
# if 0
		system("netstat | grep iocacis");
# endif
		print("  pCh->chid %s args.chid\n",
		  pCh->chid == args.chid?"==":"!=");
		print(
		  "  Channel Name: %s\n"
		  "  State: %s\n"
		  "  Native Type: %s\n"
		  "  Native Count: %hu\n"
		  "  Access: %s%s\n"
		  "  IOC: %s\n",
		  args.chid?ca_name(args.chid):"Unavailable",
		  args.chid?ca_state(args.chid) == cs_never_conn?"Never":
		  ca_state(args.chid) == cs_prev_conn?"Prev":
		  ca_state(args.chid) == cs_conn?"Conn":
		  ca_state(args.chid) == cs_closed?"Closed":"Unknown":"Unavailable",
		  args.chid?dbf_type_to_text(ca_field_type(args.chid)):"Unavailable",
		  args.chid?ca_element_count(args.chid):0,
		  args.chid?(ca_read_access(args.chid)?"R":"None"):"Unavailable",
		  args.chid?(ca_write_access(args.chid)?"W":""):"",
		  args.chid?ca_host_name(args.chid):"Unavailable");
#endif
	    }
	  /* Set this one last so ca_replace_access_rights_event can
             use the old value */
	    pCh->previouslyConnected = True;
	} else {
	  /* Connected and previously connected */
	    pCh->pr->connected = True;
	    caTask.channelConnected++;
	    if(pCh->pr->updateValueCb)
	      pCh->pr->updateValueCb((XtPointer)pCh->pr);
	}
    } else {
      /* Not connected */
	if(pCh->previouslyConnected == False) {
	  /* Not connected and not previously connected */
	  /* Probably doesn't happen -- if CA can't connect, this
	   *   routine never gets called */
	    pCh->pr->connected = False;
	} else {
	  /* Not connected but previously connected */
	    pCh->pr->connected = False;
	    caTask.channelConnected--;
	    if(pCh->pr->updateValueCb)
	      pCh->pr->updateValueCb((XtPointer)pCh->pr);
	}
    }
}
/* Find an unconnected PV and attempt to connect to it.  That should
 * restart the searches for all other unresolved PVs. */
void retryConnections(void)
{
    int i,j;
    const char *pvname=NULL;
    chid retryChid;
    int status;

#if DEBUG_RETRY
    print("retryConnections:\n");
    print(" freeListSize=%d freeListCount=%d\n",
      caTask.freeListSize,caTask.freeListCount);
    print(" pageSize=%d pageCount=%d\n",
      caTask.pageSize,caTask.pageCount);
    print(" nextpage=%d nextFree=%d\n",
      caTask.nextPage,caTask.nextFree);
    print(" channelCount=%d channelConnected=%d\n",
      caTask.channelCount,caTask.channelConnected);
    if(caTask.nextPage != caTask.pageCount-1) {
	print(" caTask.nextPage != caTask.pageCount-1\n");
    }
#else
  /* Check if all channels are connected */
    if(caTask.channelCount == caTask.channelConnected) {
	medmPostMsg(1,"retryConnections: All channels are connected\n");
	XBell(display, 50);
	return;
    }
#endif

  /* Find an unconnected PV */
    for(i=0; i < caTask.pageCount; i++) {
	int jmax=(i == caTask.nextPage)?caTask.nextFree:CA_PAGE_SIZE;
	for(j=0; j < jmax; j++) {
	    Channel *pCh=&caTask.pages[i][j];
	    if(pCh->chid && ca_state(pCh->chid) != cs_conn) {
		pvname=ca_name(pCh->chid);
		break;
	    }
	}
	if(pvname) break;
    }
#if DEBUG_RETRY
    print(" Found %s\n",pvname?pvname:"Not found");
    if(!pvname) return;
#else
    if(!pvname) {
	medmPostMsg(1,"retryConnections: Failed to find unconnected PV\n");
	return;
    }
#endif

  /* Search */
    status=ca_search_and_connect(pvname,&retryChid,NULL,NULL);
    if(status != ECA_NORMAL) {
	medmPostMsg(1,"retryConnections: ca_search failed for %s: %s\n",
	  pvname, ca_message(status));
    }

  /* Wait.  The searches will only continue for this time.  Keep the
   * time short as the interface is frozen, and most of the searches
   * occur at the start of the sequence.  Testing indicated:
   *
   * RETRY_TIMEOUT Searches
   *      30          15
   *       5          10
   *       3           9
   *       2           9
   *       1           8
   *
   * but this may vary owing to tuning and may change with new releases.
   */
    ca_pend_io(RETRY_TIMEOUT);

  /* Clear the channel */
    status = ca_clear_channel(retryChid);
    if(status != ECA_NORMAL) {
	medmPostMsg(1,"retryConnections: ca_clear_channel failed for %s: %s\n",
	  pvname, ca_message(status));
    }
}
void popupPvInfo(DisplayInfo *displayInfo)
{
    DlElement *pE;
    Record **records;
    chid chId;
    int i, status;
    Record *pR;
    Channel *pCh;
    char descName[MAX_TOKEN_LENGTH];
    char *pDot;
    double connTimeout;

#if DEBUG_PVINFO
    XUngrabPointer(display,CurrentTime);
#endif

  /* Check if another call is in progress */
    if(pvInfo) {
	medmPostMsg(1,"popupPvInfo: "
	  "Another PV Info request is already in progress\n"
	  "  It is probably having problems\n"
	  "  Wait for it to finish\n");
	return;
    }

  /* Create the dialog box if it has not been created */
    if(!pvInfoS) createPvInfoDlg();

  /* Get the records */
    records = getPvInfoFromDisplay(displayInfo, &nPvInfoPvs, &pE);
    if(!records) return;
    pvInfoElement = pE;

  /* Allocate space */
    pvInfo = (PvInfo *)calloc(nPvInfoPvs, sizeof(PvInfo));
    if(!pvInfo) {
	medmPostMsg(1,"popupPvInfo: Memory allocation error\n");
	if(records) free(records);
	if(pvInfoS && XtIsManaged(pvInfoS)) return;
    }

  /* Loop over the records, initialize, and initiate search for DESC */
    for(i=0; i < nPvInfoPvs; i++) {
      /* Initialize */
	pvInfo[i].pvChid = NULL;
	pvInfo[i].pvOk = False;
	pvInfo[i].timeOk = False;
	pvInfo[i].descChid = NULL;
	pvInfo[i].descOk = False;
	strcpy(pvInfo[i].descVal, NOT_AVAILABLE);
#if defined(DBR_CLASS_NAME) && DO_RTYP
	pvInfo[i].rtypOk = False;
	strcpy(pvInfo[i].rtypVal, NOT_AVAILABLE);
#endif

      /* Check for a valid record */
	if(records[i]) {
	    pR = pvInfo[i].record = records[i];
	    pCh = getChannelFromRecord(pR);
	    if(!pCh) continue;
	    if(!pCh->chid) continue;
	    chId = pvInfo[i].pvChid = pCh->chid;
	} else continue;
	pvInfo[i].pvOk = True;

      /* Don't try the others unless the PV is connected */
	if(ca_state(chId) != cs_conn || !ca_read_access(chId))
	  continue;

      /* Construct the DESC name */
	strcpy(descName,ca_name(chId));
	pDot = strchr(descName,'.');
	if(pDot) {
	  /* Assume it is a name with a field and replace the field
	   * with DESC */
	    strcpy(pDot,".DESC");
	} else {
	  /* Append .DESC */
	    strcat(descName,".DESC");
	}
      /* Search for the DESC */
	status = ca_search(descName, &pvInfo[i].descChid);
	if(status == ECA_NORMAL) {
	    pvInfo[i].descOk = True;
	} else {
	    medmPostMsg(1,"popupPvInfo: DESC: ca_search for %s: %s\n",
	      descName, ca_message(status));
	}
    }

  /* Free the records, they are now stored in pvInfo */
    if(records) free(records);

  /* Wait for the searches (Timeouts should be uncommon) */
    status=ca_pend_io(CA_PEND_IO_TIME);
    if(status != ECA_NORMAL) {
	medmPostMsg(1,"popupPvInfo: Waited %g seconds.  "
	  "Did not find the DESC information (%s).\n",
	  CA_PEND_IO_TIME, descName);
    }

  /* Loop over the records and do the gets */
    nPvInfoCbs = 0;
    for(i=0; i < nPvInfoPvs; i++) {
	if(!pvInfo[i].pvOk) continue;

      /* Don't try the others unless the PV is connected */
	chId = pvInfo[i].pvChid;
	if(ca_state(chId) != cs_conn || !ca_read_access(chId))
	  continue;

      /* Get the DESC */
	if(ca_state(pvInfo[i].descChid) == cs_conn &&
	  ca_read_access(pvInfo[i].descChid)) {
	  /* Do the get */
	    status = ca_get_callback(DBR_STRING, pvInfo[i].descChid,
	      pvInfoDescGetCb, &pvInfo[i]);
	    if(status == ECA_NORMAL) {
		nPvInfoCbs++;
	    } else {
		pvInfo[i].descOk = False;
		medmPostMsg(1,"pvInfoConnectCb: DESC: ca_array_get_callback"
		  " for %s: %s\n",
		  ca_name(pvInfo[i].descChid), ca_message(status));
	    }
	} else {
	    pvInfo[i].descOk = False;
	}

      /* Get the time value as a string */
	status = ca_get_callback(DBR_TIME_STRING, chId, pvInfoTimeGetCb,
	  &pvInfo[i]);
	if(status == ECA_NORMAL) {
	    nPvInfoCbs++;
	} else {
	    medmPostMsg(1,"popupPvInfo: STAMP: ca_get_callback for %s: %s\n",
	      ca_name(chId), ca_message(status));
	}

#if defined(DBR_CLASS_NAME) && DO_RTYP
      /* Get the RTYP */
	status = ca_get_callback(DBR_CLASS_NAME, chId, pvInfoRtypGetCb,
	  &pvInfo[i]);
	if(status == ECA_NORMAL) {
	    nPvInfoCbs++;
	} else {
	    medmPostMsg(1,"popupPvInfo: RTYP: ca_get_callback for %s: %s\n",
	      ca_name(chId), ca_message(status));
	}
#endif
    }

  /* Add a timeout and poll if there are callbacks
   *   The timeout is a safety net and should never be called
   *   All callbacks should come back inside the EPICS_CA_CONN_TMO
   *   Wait for 2 times this */
    if(nPvInfoCbs) {
	ca_poll();     /* May not be really necessary here */
	status = envGetDoubleConfigParam(&EPICS_CA_CONN_TMO, &connTimeout);
	if (status == 0) pvInfoTime = (unsigned long)(2000.*connTimeout+.5);
	else pvInfoTime = PVINFO_TIMEOUT;
	pvInfoTimeoutId = XtAppAddTimeOut(appContext, pvInfoTime,
	  pvInfoTimeout, NULL);
	pvInfoTimerOn = True;
    } else {
	pvInfoWriteInfo();
    }

#if DEBUG_PVINFO
    print("popupPvInfo: nPvInfoCbs=%d timeout=%ld\n",
      nPvInfoCbs, nPvInfoCbs?pvInfoTime:0L);
#endif
}