/*--------------------------------------------------------------------------- ease.c basics for (ea)sy implementable (s)ample (e)nvironment devices. handles background activity and connections over rs232. Markus Zolliker, March 2005 ---------------------------------------------------------------------------- */ #include #include #include #include #include #include #include #include "sics.h" #include "ease.h" #define EASE_FLAGBITS (8*sizeof(long)) static ParClass easeBaseClass = { "EaseBase", sizeof(EaseBase) }; static ParClass easeDrivClass = { "EaseDriv", sizeof(EaseDriv) }; static int parameterChange = 0; /*----------------------------------------------------------------------------*/ ParClass *EaseBaseClass(void) { return ParMakeClass(&easeBaseClass, NULL); } /*----------------------------------------------------------------------------*/ ParClass *EaseDrivClass(void) { return ParMakeClass(&easeDrivClass, EaseBaseClass()); } /*----------------------------------------------------------------------------*/ EaseBase *EaseBaseCast(void *object) { return ParCast(&easeBaseClass, object); } /*----------------------------------------------------------------------------*/ EaseDriv *EaseDrivCast(void *object) { return ParCast(&easeDrivClass, object); } /*----------------------------------------------------------------------------*/ void EaseStop(EaseBase *eab) { FsmStop(eab->task, eab->idle); eab->state = EASE_notconnected; } /*----------------------------------------------------------------------------*/ void EaseWriteError(EaseBase *eab) { int l; switch (eab->errCode) { case NOTCONNECTED: ParPrintf(eab, eError, "ERROR: unconnected socket for %s", eab->p.name); break; case FAILEDCONNECT: ParPrintf(eab, eError, "ERROR: can not connect to %s:%d (terminalserver off or no ethernet connection)", eab->ser->pHost, eab->ser->iPort); break; case TIMEOUT: ParPrintf(eab, eError, "ERROR: timeout on %s\ncmd: %s\nans: %s", eab->p.name, eab->cmd, eab->ans); break; case INCOMPLETE: ParPrintf(eab, eError, "ERROR: incomplete answer %s from %s", eab->ans, eab->p.name); break; case EASE_ILL_ANS: l = strlen(eab->cmd); if (l > 0 && eab->cmd[l-1] < ' ') { l--; } ParPrintf(eab, eError, "ERROR: illegal answer %s from %s (cmd: %*s)", eab->ans, eab->p.name, l, eab->cmd); break; case EASE_DEV_CHANGED: ParPrintf(eab, eError, "ERROR: controller was exchanged on %s", eab->p.name); EaseStop(eab); break; case EASE_FAULT: ParPrintf(eab, eError, "ERROR: error on %s", eab->p.name); break; default: ParPrintf(eab, eError, "ERROR: error code %d on %s", eab->errCode, eab->p.name); break; } } /*----------------------------------------------------------------------------*/ void EaseWrite(EaseBase *eab, char *cmd) { int iRet; char trash[64]; int l; if (eab->errCode || eab->state == EASE_expect) return; while (availableRS232(eab->ser) == 1) { l=sizeof(trash); iRet = readRS232TillTerm(eab->ser, trash, &l); if (iRet < 0) break; ParPrintf(eab, -2, "trash: %s\n", trash); } snprintf(eab->cmd, sizeof(eab->cmd), "%s%s", cmd, eab->ser->sendTerminator); iRet = writeRS232(eab->ser, eab->cmd, strlen(eab->cmd)); if (iRet < 0) { FsmStop(eab->task, eab->idle); snprintf(eab->msg, sizeof eab->msg, "connection to %s:%d lost", eab->ser->pHost, eab->ser->iPort); ParPrintf(eab, eError, "ERROR: %s", eab->msg); eab->state = EASE_notconnected; eab->errCode = iRet; return; } ParPrintf(eab, -2, "cmd: %s", cmd); eab->state = EASE_expect; eab->cmdtime = time(NULL); } /*----------------------------------------------------------------------------*/ int EaseWaitRead(EaseBase *eab) { int cnt=0; if (eab->state < EASE_idle) { return NOTCONNECTED; } FsmPause(eab->task, 1); while (eab->state == EASE_expect) { /* TaskYield(pServ->pTasker); does not work (pardef is not recursive) */ FsmTaskHandler(eab->task); cnt++; } FsmPause(eab->task, 0); /* if (cnt>1) printf("TaskYield %d\n", cnt); */ return 1; } /*----------------------------------------------------------------------------*/ void EaseParHasChanged(void) { parameterChange = 1; } /*----------------------------------------------------------------------------*/ void EaseSavePars(void) { char *pFile = NULL; if (parameterChange) { parameterChange = 0; assert(pServ->pSics); pFile = IFindOption(pSICSOptions,"statusfile"); if (pFile) { WriteSicsStatus(pServ->pSics,pFile,0); } } } /*----------------------------------------------------------------------------*/ int EaseHandler(EaseBase *eab) { EaseDriv *ead = EaseDrivCast(eab);; int iret; EaseSavePars(); if (ead && ead->stopped && ead->hwstate == HWBusy) { ead->stopped = 0; ead->hwstate = HWIdle; if (FsmStop(eab->task, eab->doit)) { ParPrintf(eab, eWarning, "%s stopped", eab->p.name); return 0; } } if (eab->state == EASE_expect) { if (eab->cmdtime !=0 && time(NULL) > eab->cmdtime + eab->p.period*2) { snprintf(eab->msg, sizeof eab->msg, "no response since %d sec", (int)(time(NULL) - eab->cmdtime)); } return 0; } if (eab->state == EASE_lost) { snprintf(eab->msg, sizeof eab->msg, "no response"); } if (eab->state == EASE_notconnected) { return 0; } if (eab->state == EASE_connecting) { iret = initRS232Finished(eab->ser); if (iret == 0) return 0; /* waiting for connection */ if (iret < 0) { snprintf(eab->msg, sizeof eab->msg, "connection for %s failed", eab->p.name); ParPrintf(eab, eError, "%s", eab->msg); eab->state = EASE_notconnected; return 0; } else { snprintf(eab->msg, sizeof eab->msg, "get a first answer from %s", eab->p.name); eab->state = EASE_idle; } } if (eab->errCode) { EaseWriteError(eab); eab->errCode = 0; if (ead) { ead->hwstate = HWFault; FsmStop(eab->task, eab->doit); } return 0; } if (ead) { if (eab->state == EASE_lost) { if (FsmStop(eab->task, eab->doit)) { ParPrintf(eab, eWarning, "stop %s caused by timeout", eab->p.name); } return 0; } } else { if (eab->state == EASE_lost) { return 0; } } return 1; } /*----------------------------------------------------------------------------*/ int EaseNextFullRead(EaseBase *eab) { time_t thisPeriod; thisPeriod = time(NULL) / eab->p.period; if (thisPeriod != eab->readPeriod) { eab->readPeriod = thisPeriod; return 1; } return 0; } /*----------------------------------------------------------------------------*/ static long EaseSendIt(long pc, void *object) { EaseBase *eab = EaseBaseCast(object); switch (pc) { default: /* FSM BEGIN *******************************/ EaseWrite(eab, eab->sendCmd); ParPrintf(eab, eWarning, "send cmd> %s", eab->sendCmd); eab->sendCmd = NULL; return __LINE__; case __LINE__: /**********************************/ ParPrintf(eab, eWarning, "response> %s", eab->ans); return 0; } /* FSM END ********************************************/ } /*----------------------------------------------------------------------------*/ int EaseCheckDoit(EaseBase *eab) { int i, n; if (eab->todo == NULL) { if (eab->sendCmd != NULL) { eab->todo = EaseSendIt; return 1; } n = eab->maxflag / EASE_FLAGBITS; for (i=0; i<=n; i++) { if (eab->updateFlags[i]) { eab->todo = eab->doit; return 1; } } } return eab->todo != NULL; } /*----------------------------------------------------------------------------*/ static long EaseIdle(long pc, void *object) { EaseBase *eab = EaseBaseCast(object); EaseDriv *ead = EaseDrivCast(object); struct timeval tm; FsmFunc todo; switch (pc) { default: /* FSM BEGIN *******************************/ idle: if (! EaseCheckDoit(eab)) goto rd; doit: todo = eab->todo; eab->todo = NULL; FsmCall(todo); return __LINE__; case __LINE__: /**********************************/ eab->startOk = 1; rd: /* if (eab->state == EASE_lost) { snprintf(eab->msg, sizeof eab->msg, "no response from %s", eab->p.type->name); } */ FsmCall(eab->read); return __LINE__; case __LINE__: /**********************************/ ParLog(eab); /* just for the case ParLog was not included in the read function */ if (EaseCheckDoit(eab)) goto doit; /* gettimeofday(&tm, NULL); printf("stop %s %f\n", eab->evc->pName, tm.tv_usec / 1e6); */ FsmWait(1); return __LINE__; case __LINE__: /**********************************/ /* gettimeofday(&tm, NULL); printf("start %s %f\n", eab->evc->pName, tm.tv_usec / 1e6); */ goto idle; /* never return */ return 0; } /* FSM END ********************************************/ } /*----------------------------------------------------------------------------*/ static float EaseGetValue(void *obj, SConnection *pCon) { float val; EaseBase *eab; assert(eab = EaseBaseCast(obj)); ParGetFloat(pCon, eab, "", &val); return val; } /*----------------------------------------------------------------------------*/ int EaseUpdate(int flag) { EaseBase *eab = EaseBaseCast(ParObject()); ParAccess(usUser); ParSave(0); if (ParActionIs(PAR_SET) > 0) { assert(flag >= 0); assert(flag <= eab->maxflag); eab->updateFlags[flag / EASE_FLAGBITS] |= 1 << (flag % EASE_FLAGBITS); FsmSpeed(eab->task); return 1; } return 0; } /*----------------------------------------------------------------------------*/ int EaseNextUpdate(void *object) { EaseBase *eab = EaseBaseCast(object); unsigned long mask, p; int i, n, flag; n = eab->maxflag / EASE_FLAGBITS; for (i = 0; i <= n; i++) { mask = eab->updateFlags[i]; p = 1; flag = 0; /* find first */ while (flag < 32) { if (mask & p) { eab->updateFlags[i] &= ~ p; return flag + i * EASE_FLAGBITS; } p <<= 1; flag++; } } return -1; } /*----------------------------------------------------------------------------*/ int EaseGetUpdate(void *object, int flag) { EaseBase *eab = EaseBaseCast(object); int i; assert(flag >= 0); assert(flag <= eab->maxflag); i = flag / EASE_FLAGBITS; if ((1 << (flag % EASE_FLAGBITS)) & eab->updateFlags[i]) { return flag; } return 0; } /*----------------------------------------------------------------------------*/ void EaseSetUpdate(void *object, int flag, int state) { EaseBase *eab = EaseBaseCast(object); assert(flag >= 0); assert(flag <= eab->maxflag); if (state) { eab->updateFlags[flag / EASE_FLAGBITS] |= 1 << (flag % EASE_FLAGBITS); } else { eab->updateFlags[flag / EASE_FLAGBITS] &= ~ (1 << (flag % EASE_FLAGBITS)); } } /*----------------------------------------------------------------------------*/ static long EaseRun(void *obj, SConnection *pCon, float fVal) { EaseBase *eab = EaseBaseCast(obj); EaseDriv *ead = EaseDrivCast(obj); assert(ead ); SCSave(&eab->p.conn, pCon); if (! eab->doit) { ParPrintf(ead, eError, "ERROR: missing run function %s", eab->p.name); return 0; } if (!eab->startOk) { ParPrintf(ead, eError, "ERROR: %s is not ready to run", eab->p.name); return 0; } if (FsmStop(eab->task, eab->doit)) { ParPrintf(ead, eWarning, "running %s cancelled", eab->p.name); } if (eab->todo) { ParPrintf(ead, eError, "ERROR: %s busy", eab->p.name); return 0; } ead->targetValue = fVal; EaseParHasChanged(); /* assume that targetValue has to be saved */ ead->stopped = 0; /* eab->todo = eab->doit; */ EaseSetUpdate(eab, EASE_RUN, 1); ead->hwstate = HWBusy; if (ead->maxwait >= 0) { ead->timeout = time(NULL) + ead->maxwait; } else { ead->maxwait = -1; ead->timeout = time(NULL) + 9999999; /* approx. infinite */ } ead->finish = 0; ead->usedSettle = 0; ead->tolState = EASE_outOfTolerance; return 1; } /*----------------------------------------------------------------------------*/ static int EaseHalt(void *obj) { EaseDriv *ead; assert(ead = EaseDrivCast(obj)); ead->stopped = 1; return 1; } /*----------------------------------------------------------------------------*/ static int EaseIsInTol(void *obj) { EaseDriv *ead = EaseDrivCast(obj); float f; f = EaseGetValue(ead, NULL) - ead->targetValue; if (ead->tolState == EASE_outOfTolerance) { if (fabsf(f) < ead->tolerance) { ParPrintf(obj, eWarning, "%s is within tolerance", ead->b.p.name); ead->tolState = EASE_inTolerance; return 1; } return 0; } else { if (fabsf(f) > ead->tolerance * 1.11) { if (ead->tolState == EASE_inTolerance) { ParPrintf(obj, eWarning, "%s is out of tolerance by %f", ead->b.p.name, f); } ead->tolState = EASE_outOfTolerance; return 0; } return 1; } } /*----------------------------------------------------------------------------*/ static int EaseErrHandler(void *obj) { /* to be implemented */ return 1; } /*----------------------------------------------------------------------------*/ static int EaseCheckStatus(void *obj, SConnection *pCon) { EaseDriv *ead = EaseDrivCast(obj); time_t now, t; assert(ead); SCSave(&ead->b.p.conn, pCon); if (ead->stopped) { return HWIdle; } now = time(NULL); if (now > ead->timeout) { ParPrintf(obj, eWarning, "maxwait expired when driving %s", ead->b.p.name); ead->hwstate = HWIdle; } else if (EaseIsInTol(obj)) { if (ead->finish == 0) { ead->finish = now - ead->usedSettle; if (now < ead->finish + ead->settle) { ParPrintf(obj, eWarning, "settle %s for %ld sec", ead->b.p.name, (long)(ead->finish + ead->settle - now)); } } if (now > ead->finish + ead->settle && ead->hwstate != HWIdle) { ParPrintf(obj, eWarning, "%s has reached target", ead->b.p.name); ead->hwstate = HWIdle; } } else if (ead->finish != 0) { ead->usedSettle = now - ead->finish; ead->finish = 0; } return ead->hwstate; } /*----------------------------------------------------------------------------*/ static EVMode EaseGetMode(void *obj) { EaseDriv *ead = EaseDrivCast(obj); assert(ead); if (ead->hwstate == HWBusy) { return EVDrive; } if (ead->tolState == EASE_notMonitored) { return EVIdle; } return EVMonitor; } /*----------------------------------------------------------------------------*/ static int EaseCheckLimits(void *obj, float fVal, char *error, int errLen) { EaseDriv *ead; assert(ead = EaseDrivCast(obj)); /* lower limit */ if (fVal < ead->lowerLimit) { snprintf(error, errLen, "ERROR: %g violates lower limit of device",fVal); return 0; } /* upper limit */ if (fVal > ead->upperLimit) { snprintf(error, errLen, "ERROR: %g violates upper limit of device",fVal); return 0; } return 1; } /*----------------------------------------------------------------------------*/ static int EaseRestart(EaseBase *eab) { int iRet; if (eab->task) { FsmStop(eab->task, eab->idle); FsmRestartTask(eab->task, eab->idle); } eab->startOk = 0; eab->todo = eab->start; eab->state = EASE_connecting; iRet = initRS232WithFlags(eab->ser, 3); if (iRet != 1) { eab->errCode = iRet; EaseWriteError(eab); return -1; } snprintf(eab->msg, sizeof eab->msg, "connecting to %s:%d", eab->ser->pHost, eab->ser->iPort); return 0; } /*----------------------------------------------------------------------------*/ static int EaseInit(SConnection *pCon, EaseBase *eab, int argc, char *argv[], int maxflag, FsmHandler handler, FsmFunc start, FsmFunc idle, FsmFunc read) { /* args (starting from argv[0]): : */ int port, iRet, i; rs232 *ser; char *colon, *host; char buf[64]; assert(eab); assert(handler); assert(start); assert(read || idle); eab->handler = handler; eab->start = start; eab->read = read; if (idle) { eab->idle = idle; } else { eab->idle = EaseIdle; } if (argc < 1 || argc > 2) { SCWrite(pCon, "illegal number of arguments", eError); return 0; } colon = strchr(argv[0], ':'); if (!colon && argc == 1) { /* use a rs232 object */ ser = FindCommandData(pServ->pSics, argv[0], "RS232 Controller"); if (ser == NULL) { SCWrite(pCon, "ERROR: rs232 not found", eError); return 0; } } else { if (argc == 1) { /* host:port syntax */ port=atoi(colon+1); i = colon-argv[0]; if (i >= sizeof buf) { SCWrite(pCon, "ERROR: host name too long", eError); return 0; } strncpy(buf, argv[0], i); buf[i]='\0'; host = buf; } else if (argc == 2) { /* host port syntax */ host = argv[0]; port=atoi(argv[1]); } if (port==0) { SCWrite(pCon, "ERROR: illegal port number", eError); return 0; } ser = createRS232(host, port); if (ser == NULL) { SCWrite(pCon, "out of memory", eError); return 0; } } eab->ser = ser; eab->startOk = 0; eab->errCode = 0; eab->cmdtime = 0; eab->version[0] = '\0'; eab->maxflag = maxflag; eab->sendCmd = NULL; eab->updateFlags = calloc(maxflag / EASE_FLAGBITS + 1, sizeof (*eab->updateFlags)); if (eab->updateFlags == NULL) { SCWrite(pCon, "out of memory", eError); return 0; } setRS232ReplyTerminator(eab->ser,"\r"); setRS232SendTerminator(eab->ser,"\r"); setRS232Timeout(eab->ser,10000); /* milliseconds */ setRS232Debug(eab->ser,0); eab->task = NULL; if (EaseRestart(eab) < 0) return -1; eab->task = FsmStartTask(eab, eab->handler, eab->idle); TaskRegister(pServ->pTasker, (TaskFunc)FsmTaskHandler, NULL, FsmKill, eab->task, 0); return 1; } /*----------------------------------------------------------------------------*/ static void *EaseGetInterface(void *obj, int iCode) { EaseBase *eab = EaseBaseCast(obj); EaseDriv *ead = EaseDrivCast(obj); assert(eab); if (iCode == DRIVEID) { if (ead) { return ead->drivInt; } else { return NULL; } } else if (iCode == ENVIRINTERFACE) { return ead->evInt; } return NULL; } /*----------------------------------------------------------------------------*/ void *EaseMakeBase(SConnection *con, void *class, int argc, char *argv[], int dynamic, int maxflag, ParDef pardef, FsmHandler handler, FsmFunc start, FsmFunc idle, FsmFunc read) { /* args (starting from argv[0]): MakeObject objectname */ EaseBase *eab; char *creationCmd = NULL; if (dynamic) { creationCmd = ParArg2Text(argc, argv, NULL, 0); } eab = ParMake(con, argv[1], class, pardef, creationCmd); if (!eab) return NULL; ParCheck(&easeBaseClass, eab); if (! EaseInit(con, eab, argc-3, argv+3, maxflag, handler, start, idle, read)) { RemoveCommand(pServ->pSics, argv[1]); return NULL; } return eab; } /*----------------------------------------------------------------------------*/ void *EaseMakeDriv(SConnection *con, void *class, int argc, char *argv[], int dynamic, int maxflag, ParDef pardef, FsmHandler handler, FsmFunc start, FsmFunc idle, FsmFunc read, FsmFunc run) { int iret; EaseDriv *ead; char *creationCmd = NULL; assert(run); if (dynamic) { creationCmd = ParArg2Text(argc, argv, NULL, 0); } ead = ParMake(con, argv[1], class, pardef, creationCmd); if (!ead) return NULL; ParCheck(&easeDrivClass, ead); ead->b.doit = run; ead->evInt = CreateEVInterface(); ead->drivInt = CreateDrivableInterface(); ead->b.p.desc->GetInterface = EaseGetInterface; if (! ead->evInt || ! ead->drivInt || ! EaseInit(con, (EaseBase *)ead, argc-3, argv+3, maxflag, handler, start, idle, read)) { RemoveCommand(pServ->pSics, argv[1]); return NULL; } ead->evInt->GetMode = EaseGetMode; ead->evInt->IsInTolerance = EaseIsInTol; ead->evInt->HandleError = EaseErrHandler; ead->hwstate = HWIdle; ead->drivInt->Halt = EaseHalt; ead->drivInt->CheckLimits = EaseCheckLimits; ead->drivInt->SetValue = EaseRun; ead->drivInt->CheckStatus = EaseCheckStatus; ead->drivInt->GetValue = EaseGetValue; /* EMon interface to be implemented */ return ead; } /*----------------------------------------------------------------------------*/ void EaseMsgPar(void *object) { EaseBase *eab = EaseBaseCast(object); assert(eab); ParName("status"); ParLogAs(NULL); if (eab->msg[0] == '\0') { ParList(""); /* do not list when empty */ } else { ParList(NULL); } ParFixedStr(eab->msg, sizeof eab->msg, NULL); return; } /*----------------------------------------------------------------------------*/ int EaseRestartWrapper(void *object, void *userarg, int argc, char *argv[]) { EaseBase *eab = EaseBaseCast(object); EaseRestart(eab); return 0; } /*----------------------------------------------------------------------------*/ void EaseBasePar(void *object) { EaseBase *eab = EaseBaseCast(object); assert(eab); ParName("restart"); ParAccess(usUser); ParCmd(EaseRestartWrapper, NULL); if (ParActionIs(PAR_KILL)) { if (eab->ser) { KillRS232(eab->ser); free(eab->ser); eab->ser = NULL; } if (pServ->pTasker && eab->task) { FsmStopTask(eab->task); } if (eab->updateFlags) { free(eab->updateFlags); eab->updateFlags = NULL; } } return; } /*----------------------------------------------------------------------------*/ int EaseSend(void *object, void *userarg, int argc, char *argv[]) { EaseBase *eab = EaseBaseCast(object); int iret; char cmd[32]; char ans[64]; char *term; iret = EaseWaitRead(eab); if (iret >= 0) { eab->sendCmd = ParArg2Text(argc, argv, NULL, 0); ParPrintf(eab, -2, "ans: %s", ans); ParPrintf(eab, eValue, "%s", ans); } if (iret < 0) { eab->errCode = iret; EaseWriteError(eab); } return 0; } /*----------------------------------------------------------------------------*/ void EaseSendPar(void *object) { ParName("send"); ParAccess(usUser); ParCmd(EaseSend, NULL); return; } /*----------------------------------------------------------------------------*/ void EaseDrivPar(void *object, char *fmt, char *unit) { EaseDriv *ead; assert(ead = EaseDrivCast(object)); ParName("upperLimit"); ParFmt(fmt); ParTail(unit); ParAccess(usUser); ParSave(1); ParFloat(&ead->upperLimit, 310.); ParName("lowerLimit"); if (ead->lowerLimit != 0) { ParFmt(fmt); ParTail(unit); }; ParAccess(usUser); ParSave(1); ParFloat(&ead->lowerLimit, 0.0); ParName("tolerance"); ParFmt(fmt); ParTail(unit); ParAccess(usUser); ParSave(1); ParFloat(&ead->tolerance, 0.0); ParName("maxwait"); if (ead->maxwait < 0) { ParTail("disabled"); } else { ParTail("sec"); } ParAccess(usUser); ParSave(1); ParInt(&ead->maxwait, 7200); ParName("settle"); ParTail("sec"); ParAccess(usUser); ParSave(1); ParInt(&ead->settle, 0); ParName("targetValue"); ParFmt(fmt); ParTail(unit); ParFloat(&ead->targetValue, PAR_NAN); if (ParActionIs(PAR_KILL) && ead->drivInt) { free(ead->drivInt); } return; }