Files
sics/selector.c

793 lines
22 KiB
C

/*--------------------------------------------------------------------------
S E L E C T O R
This is the implementation file for the the code necessary to deal
with an energy selector, monochromator. This is the Crystal
variety.
Mark Koennecke, January 1997
Copyright:
Labor fuer Neutronenstreuung
Paul Scherrer Institut
CH-5423 Villigen-PSI
The authors hereby grant permission to use, copy, modify, distribute,
and license this software and its documentation for any purpose, provided
that existing copyright notices are retained in all copies and that this
notice is included verbatim in any distributions. No written agreement,
license, or royalty fee is required for any of the authorized uses.
Modifications to this software may be copyrighted by their authors
and need not follow the licensing terms described here, provided that
the new terms are clearly indicated on the first page of each file where
they apply.
IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
MODIFICATIONS.
----------------------------------------------------------------------------*/
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <math.h>
#include "fortify.h"
#include "sics.h"
#include "motor.h"
#include "splitter.h"
#include "devexec.h"
#include "selector.h"
#define SS 0
#define B1C1 1
#define B1C2 2
#define B1MIN 3
#define B1MAX 4
#define B2C1 5
#define B2C2 6
#define B2MIN 7
#define B2MAX 8
#define LATD 9
#define RIGHTS 10
/*------------------------------------------------------------------------*/
typedef struct __SicsSelector {
ObjectDescriptor *pDes;
ObPar *pParams;
char *name;
pMotor pTheta;
pMotor pTwoTheta;
pMotor pBend1;
pMotor pBend2;
char *pType;
} SicsSelector;
/*-------------------------------------------------------------------------*/
char *MonoGetType(pSicsSelector self)
{
assert(self);
return self->pType;
}
/*---------------------------------------------------------------------------*/
pSicsSelector CreateSelector(char *name, pMotor pTheta, pMotor pTwoTheta,
pMotor pBend1, pMotor pBend2)
{
pSicsSelector pRes = NULL;
int iRet;
float fVal;
assert(pTheta);
assert(pTwoTheta);
/* allocate memory */
pRes = (pSicsSelector) malloc(sizeof(SicsSelector));
if (!pRes) {
return NULL;
}
/* create ObjectDescriptor */
pRes->pDes = CreateDescriptor("CrystalSelector");
if (!pRes->pDes) {
free(pRes);
return NULL;
}
/* create Parameter Array */
pRes->pParams = ObParCreate(11);
if (!pRes->pParams) {
free(pRes->pDes);
free(pRes);
return NULL;
}
/* create all the parameters */
ObParInit(pRes->pParams, SS, "ss", 1., usUser);
ObParInit(pRes->pParams, B1C1, "vk1", 1., usInternal);
ObParInit(pRes->pParams, B1C2, "vk2", 1., usInternal);
ObParInit(pRes->pParams, B2C1, "hk1", 1., usInternal);
ObParInit(pRes->pParams, B2C2, "hk2", 1., usInternal);
ObParInit(pRes->pParams, LATD, "dd", 2.087, usMugger);
ObParInit(pRes->pParams, RIGHTS, "access", usUser, usMugger);
/* assign motors */
pRes->pTheta = pTheta;
pRes->pTwoTheta = pTwoTheta;
/* provide default values for Bender Parameters in order to make
things look nice
*/
fVal = 0.;
ObParInit(pRes->pParams, B1MIN, "vmin", fVal, usInternal);
ObParInit(pRes->pParams, B1MAX, "vmax", fVal, usInternal);
ObParInit(pRes->pParams, B2MIN, "hmin", fVal, usInternal);
ObParInit(pRes->pParams, B2MAX, "hmax", fVal, usInternal);
/* handle benders, if present */
pRes->pBend1 = pBend1;
if (pBend1) {
iRet = MotorGetPar(pBend1, "hardlowerlim", &fVal);
assert(iRet);
ObParInit(pRes->pParams, B1MIN, "vmin", fVal, usInternal);
iRet = MotorGetPar(pBend1, "hardupperlim", &fVal);
assert(iRet);
ObParInit(pRes->pParams, B1MAX, "vmax", fVal, usInternal);
}
pRes->pBend2 = pBend2;
if (pBend2) {
iRet = MotorGetPar(pBend2, "hardlowerlim", &fVal);
assert(iRet);
ObParInit(pRes->pParams, B2MIN, "hmin", fVal, usInternal);
iRet = MotorGetPar(pBend2, "hardupperlim", &fVal);
assert(iRet);
ObParInit(pRes->pParams, B2MAX, "hmax", fVal, usInternal);
}
pRes->name = strdup(name);
pRes->pType = strdup("Unknown");
return pRes;
}
/*---------------------------------------------------------------------------*/
void DeleteSelector(void *self)
{
pSicsSelector pSelf;
assert(self);
pSelf = (pSicsSelector) self;
if (pSelf->pDes) {
DeleteDescriptor(pSelf->pDes);
}
if (pSelf->pParams) {
ObParDelete(pSelf->pParams);
}
if (pSelf->name) {
free(pSelf->name);
}
if (pSelf->pType) {
free(pSelf->pType);
}
free(pSelf);
}
/*--------------------------------------------------------------------------
Syntax:
MonoInit name Type ThetaMotor TwoThetaMotor Bend1Motor Bend2Motor
with Bend1Motor, Bend2Motor beeing optional.
*/
int MonoInit(SConnection * pCon, SicsInterp * pSics, void *pData,
int argc, char *argv[])
{
pSicsSelector pRes = NULL;
pMotor pTheta = NULL;
pMotor pTwoTheta = NULL;
pMotor pBend1 = NULL;
pMotor pBend2 = NULL;
TokenList *pList = NULL;
int iRet = 0;
TokenList *pCurrent;
char pBueffel[132];
char pName[132];
char *pType = NULL;
assert(pCon);
assert(pSics);
/* split arguments */
argtolower(argc, argv);
pList = SplitArguments(argc, argv);
if (!pList) {
snprintf(pBueffel,sizeof(pBueffel)-1, "ERROR: parsing arguments in %s", argv[0]);
SCWrite(pCon, pBueffel, eError);
return 0;
}
/* advance and search name */
pCurrent = pList->pNext;
if (!pCurrent) {
snprintf(pBueffel,sizeof(pBueffel)-1, "ERRROR: Insufficient number of arguments to %s",
argv[0]);
SCWrite(pCon, pBueffel, eError);
goto end;
}
strcpy(pName, pCurrent->text);
/* advance and find Type string */
pCurrent = pCurrent->pNext;
if (!pCurrent) {
snprintf(pBueffel,sizeof(pBueffel)-1, "ERRROR: Insufficient number of arguments to %s",
argv[0]);
SCWrite(pCon, pBueffel, eError);
goto end;
}
pType = pCurrent->text;
/* advance and find Theta motor */
pCurrent = pCurrent->pNext;
if (!pCurrent) {
snprintf(pBueffel,sizeof(pBueffel)-1, "ERRROR: Insufficient number of arguments to %s",
argv[0]);
SCWrite(pCon, pBueffel, eError);
goto end;
}
pTheta = FindMotor(pSics, pCurrent->text);
if (!pTheta) {
snprintf(pBueffel,sizeof(pBueffel)-1, "ERROR: Cannot find motor %s for driving Theta",
pCurrent->text);
SCWrite(pCon, pBueffel, eError);
goto end;
}
/* advance and find Two Theta motor */
pCurrent = pCurrent->pNext;
if (!pCurrent) {
snprintf(pBueffel,sizeof(pBueffel)-1, "ERRROR: Insufficient number of arguments to %s",
argv[0]);
SCWrite(pCon, pBueffel, eError);
goto end;
}
pTwoTheta = FindMotor(pSics, pCurrent->text);
if (!pTwoTheta) {
snprintf(pBueffel,sizeof(pBueffel)-1, "ERROR: Cannot find motor %s for driving Two Theta",
pCurrent->text);
SCWrite(pCon, pBueffel, eError);
goto end;
}
iRet = 1; /* we are now able to install a monochromator */
/* try find first bending motor */
pCurrent = pCurrent->pNext;
if (pCurrent) {
pBend1 = FindMotor(pSics, pCurrent->text);
if (!pBend1) {
snprintf(pBueffel,sizeof(pBueffel)-1,
"ERROR: Cannot find motor %s for driving vertical bender",
pCurrent->text);
SCWrite(pCon, pBueffel, eError);
goto end;
}
} else {
goto end;
}
/* find second bender */
pCurrent = pCurrent->pNext;
if (pCurrent) {
pBend2 = FindMotor(pSics, pCurrent->text);
if (!pBend2) {
snprintf(pBueffel,sizeof(pBueffel)-1,
"ERROR: Cannot find motor %s for driving horizontal bender",
pCurrent->text);
SCWrite(pCon, pBueffel, eError);
goto end;
}
}
end:
if (iRet) {
pRes = CreateSelector(pName, pTheta, pTwoTheta, pBend1, pBend2);
if (!pRes) {
iRet = 0;
} else {
if (pRes->pType) {
free(pRes->pType);
}
pRes->pType = strdup(pType);
iRet =
AddCommand(pSics, pName, MonoAction, DeleteSelector,
(void *) pRes);
if (!iRet) {
snprintf(pBueffel,sizeof(pBueffel)-1, "ERROR: duplicate command %s not created",
argv[2]);
SCWrite(pCon, pBueffel, eError);
DeleteTokenList(pList);
DeleteSelector((void *) pRes);
return 0;
}
}
}
DeleteTokenList(pList);
return iRet;
}
/*---------------------------------------------------------------------------
a private function used in MonoAction to print all relevant features of
the monochromator
*/
static void MonoList(pSicsSelector self, SConnection * pCon)
{
char pBueffel[512];
int i, iLen;
/* print known parameters */
iLen = ObParLength(self->pParams);
snprintf(pBueffel,sizeof(pBueffel)-1, "Parameter Listing for %s\n", self->name);
SCWrite(pCon, pBueffel, eValue);
for (i = 0; i < iLen; i++) {
snprintf(pBueffel,sizeof(pBueffel)-1, "%s.%s = %f\n", self->name,
self->pParams[i].name, self->pParams[i].fVal);
SCWrite(pCon, pBueffel, eValue);
}
/* print motornames as well */
snprintf(pBueffel,sizeof(pBueffel)-1, "%s.ThetaMotor = %s\n", self->name,
self->pTheta->name);
SCWrite(pCon, pBueffel, eValue);
snprintf(pBueffel,sizeof(pBueffel)-1, "%s.TwoThetaMotor = %s\n", self->name,
self->pTwoTheta->name);
SCWrite(pCon, pBueffel, eValue);
if (self->pBend1) {
snprintf(pBueffel,sizeof(pBueffel)-1, "%s.VerticalBenderMotor = %s\n", self->name,
self->pBend1->name);
SCWrite(pCon, pBueffel, eValue);
}
if (self->pBend2) {
snprintf(pBueffel,sizeof(pBueffel)-1, "%s.HorizontalBenderMotor = %s\n", self->name,
self->pBend2->name);
SCWrite(pCon, pBueffel, eValue);
}
}
/*-----------------------------------------------------------------------------
The Action function. Syntax:
mononame list -- list all pars
mononame parname -- prints parameter
mononame parname value -- tries changing the parameter
*/
int MonoAction(SConnection * pCon, SicsInterp * pSics, void *pData,
int argc, char *argv[])
{
pSicsSelector pSelf = NULL;
TokenList *pList = NULL;
TokenList *pCurrent;
int iRet;
char pBueffel[132];
char pName[132];
float fVal;
ObPar *pPar = NULL;
assert(pCon);
assert(pSics);
assert(pData);
pSelf = (pSicsSelector) pData;
/* split arguments */
argtolower(argc, argv);
pList = SplitArguments(argc, argv);
if (!pList) {
snprintf(pBueffel,sizeof(pBueffel)-1, "ERROR: parsing arguments in %s", argv[0]);
SCWrite(pCon, pBueffel, eError);
return 0;
}
pCurrent = pList->pNext;
/* now we can have "list" or a parametername */
/* check for list first */
if (!pCurrent) {
snprintf(pBueffel,sizeof(pBueffel)-1, "ERROR: Insufficient number of arguments to %s",
argv[0]);
SCWrite(pCon, pBueffel, eError);
goto end;
}
if (strcmp(pCurrent->text, "list") == 0) {
MonoList(pSelf, pCon);
iRet = 1;
goto end;
}
/* must be parametername now */
strcpy(pName, pCurrent->text);
/* find out if it is a set or a get. On set there is another paremeter,
else none
*/
pCurrent = pCurrent->pNext;
if (pCurrent) { /* set */
/* check if input is proper */
if (pCurrent->Type == eFloat) {
fVal = pCurrent->fVal;
} else if (pCurrent->Type == eInt) {
fVal = (float) pCurrent->iVal;
} else {
snprintf(pBueffel,sizeof(pBueffel)-1, "ERROR: Illegal parameter %s given to %s",
pCurrent->text, argv[0]);
SCWrite(pCon, pBueffel, eError);
goto end;
}
iRet = ObParSet(pSelf->pParams, argv[0], pName, fVal, pCon);
goto end;
} else { /* get */
pPar = ObParFind(pSelf->pParams, pName);
if (!pPar) {
snprintf(pBueffel,sizeof(pBueffel)-1, "ERROR: Parameter %s not found in %s", pName,
argv[0]);
SCWrite(pCon, pBueffel, eError);
goto end;
} else {
snprintf(pBueffel,sizeof(pBueffel)-1, "%s.%s = %f", argv[0], pName, pPar->fVal);
SCWrite(pCon, pBueffel, eValue);
iRet = 1;
DeleteTokenList(pList);
return 1;
}
}
end:
DeleteTokenList(pList);
if (iRet)
SCSendOK(pCon);
return iRet;
}
/*--------------------------------------------------------------------------*/
struct SelPos {
float fTheta, fTwoTheta, fVert, fHor;
};
#define PI 3.14159265358979323846264338327950
#define RD 57.29577951308232087679815481410517
/* ------------------- C has no proper abs -------------------------------*/
static double absd(double f)
{
if (f < .0) {
f = -f;
}
return f;
}
/*-------------------------------------------------------------------------*/
static struct SelPos CalculatePosition(pSicsSelector self,
float fWaveLength)
{
struct SelPos pRes;
double fD;
/* Theta, TwoTheta first */
fD = fWaveLength / (2.0 * ObVal(self->pParams, LATD));
if (fD > 1.0) {
pRes.fTheta = 1000.; /* error indication: energy to big */
return pRes;
}
fD = asin(fD);
pRes.fTheta = fD * ObVal(self->pParams, SS) * RD;
pRes.fTwoTheta = 2 * pRes.fTheta;
/* now first bender */
if (self->pBend1) {
pRes.fVert = ObVal(self->pParams, B1C1) + (ObVal(self->pParams, B1C2) /
sin(fD));
}
/* now second bender */
if (self->pBend2) {
pRes.fHor = ObVal(self->pParams, B2C1) + (ObVal(self->pParams, B2C2) /
sin(fD));
}
return pRes;
}
/*--------------------------------------------------------------------------*/
int MonoLimits(pSicsSelector self, float fWaveLength,
char *error, int iErrLen)
{
struct SelPos sNeu;
char pBueffel[132];
pIDrivable pDrivInt = NULL;
/* get Position */
sNeu = CalculatePosition(self, fWaveLength);
if (sNeu.fTheta > 900.) { /* invalid wavelength or energy */
snprintf(pBueffel,sizeof(pBueffel)-1, "ERROR: Invalid wavelength or energy to high: %f",
fWaveLength);
strlcpy(error, pBueffel, iErrLen - 1);
return 0;
}
/* check each motor in turn */
pDrivInt =
self->pTheta->pDescriptor->GetInterface(self->pTheta, DRIVEID);
if (!pDrivInt->CheckLimits(self->pTheta, sNeu.fTheta, error, iErrLen)) {
return 0;
}
pDrivInt = self->pTwoTheta->pDescriptor->GetInterface(self->pTwoTheta,
DRIVEID);
if (!pDrivInt->CheckLimits(self->pTwoTheta, sNeu.fTwoTheta,
error, iErrLen)) {
return 0;
}
if (self->pBend1) {
pDrivInt =
self->pBend1->pDescriptor->GetInterface(self->pBend1, DRIVEID);
if (!pDrivInt->CheckLimits(self->pBend1, sNeu.fVert, error, iErrLen)) {
return 0;
}
}
if (self->pBend2) {
pDrivInt =
self->pBend2->pDescriptor->GetInterface(self->pBend2, DRIVEID);
if (!pDrivInt->CheckLimits(self->pBend2, sNeu.fHor, error, iErrLen)) {
return 0;
}
}
return 1;
}
/*--------------------------------------------------------------------------*/
int MonoHalt(pSicsSelector self)
{
pIDrivable pDrivInt = NULL;
/* halt each motor in turn */
pDrivInt =
self->pTheta->pDescriptor->GetInterface(self->pTheta, DRIVEID);
if (pDrivInt) {
pDrivInt->Halt(self->pTheta);
}
pDrivInt = self->pTwoTheta->pDescriptor->GetInterface(self->pTwoTheta,
DRIVEID);
if (pDrivInt) {
pDrivInt->Halt(self->pTwoTheta);
}
if (self->pBend1) {
pDrivInt = self->pBend1->pDescriptor->GetInterface(self->pBend1,
DRIVEID);
if (pDrivInt) {
pDrivInt->Halt(self->pBend1);
}
}
if (self->pBend2) {
pDrivInt = self->pBend2->pDescriptor->GetInterface(self->pBend2,
DRIVEID);
if (pDrivInt) {
pDrivInt->Halt(self->pBend2);
}
}
return 1;
}
/*-------------------------------------------------------------------------*/
int MonoRun(pSicsSelector self, SConnection * pCon, float fWaveLength)
{
struct SelPos sNeu;
char pBueffel[132];
int iRet;
/* Check authorisation */
if (!SCMatchRights(pCon, (int) ObVal(self->pParams, RIGHTS))) {
snprintf(pBueffel,sizeof(pBueffel)-1,
"ERROR: You are not authorised to move the monochromator %s",
self->name);
SCWrite(pCon, pBueffel, eError);
return 0;
}
/* get Position */
sNeu = CalculatePosition(self, fWaveLength);
if (sNeu.fTheta > 900.) { /* invalid wavelength or energy */
snprintf(pBueffel,sizeof(pBueffel)-1, "ERROR: Invalid wavelength or energy to high: %f",
fWaveLength);
return 0;
}
/* start each motor in turn */
iRet = StartDevice(GetExecutor(), self->pTheta->name,
self->pTheta->pDescriptor,
self->pTheta, pCon, pCon->runLevel, sNeu.fTheta);
if (!iRet) {
return 0;
}
iRet = StartDevice(GetExecutor(), self->pTwoTheta->name,
self->pTwoTheta->pDescriptor,
self->pTwoTheta, pCon, pCon->runLevel, sNeu.fTwoTheta);
if (!iRet) {
return 0;
}
/* bending motors */
if (self->pBend1) {
iRet = StartDevice(GetExecutor(), self->pBend1->name,
self->pBend1->pDescriptor,
self->pBend1, pCon, pCon->runLevel, sNeu.fVert);
if (!iRet) {
return 0;
}
}
if (self->pBend2) {
iRet = StartDevice(GetExecutor(), self->pBend2->name,
self->pBend2->pDescriptor,
self->pBend2, pCon, pCon->runLevel, sNeu.fHor);
if (!iRet) {
return 0;
}
}
return OKOK;
}
/*--------------------------------------------------------------------------*/
int MonoCheck(pSicsSelector self, SConnection * pCon)
{
int iRet;
pIDrivable pDrivInt = NULL;
/* cheack each motor in turn */
pDrivInt =
self->pTheta->pDescriptor->GetInterface(self->pTheta, DRIVEID);
if (pDrivInt) {
iRet = pDrivInt->CheckStatus(self->pTheta, pCon);
if ((iRet != OKOK) && (iRet != HWIdle)) {
return iRet;
}
}
pDrivInt = self->pTwoTheta->pDescriptor->GetInterface(self->pTwoTheta,
DRIVEID);
if (pDrivInt) {
iRet = pDrivInt->CheckStatus(self->pTwoTheta, pCon);
if ((iRet != OKOK) && (iRet != HWIdle)) {
return iRet;
}
}
if (self->pBend1) {
pDrivInt = self->pBend1->pDescriptor->GetInterface(self->pBend1,
DRIVEID);
if (pDrivInt) {
iRet = pDrivInt->CheckStatus(self->pBend1, pCon);
if ((iRet != OKOK) && (iRet != HWIdle)) {
return iRet;
}
}
}
if (self->pBend2) {
pDrivInt = self->pBend2->pDescriptor->GetInterface(self->pBend2,
DRIVEID);
if (pDrivInt) {
iRet = pDrivInt->CheckStatus(self->pBend2, pCon);
if ((iRet != OKOK) && (iRet != HWIdle)) {
return iRet;
}
}
}
return HWIdle;
}
/*--------------------------------------------------------------------------
returns the current Wavelength the monochromator is adjusted to
*/
float GetMonoPosition(pSicsSelector self, SConnection * pCon)
{
double fVal, dTheta;
float fTheta, fTwoTheta;
char pBueffel[132];
int iRet;
/* get the two positions */
iRet = MotorGetSoftPosition(self->pTheta, pCon, &fTheta);
if (!iRet) {
snprintf(pBueffel,sizeof(pBueffel)-1,
"ERROR: cannot read Theta motor for monochromator %s\n",
self->name);
SCWrite(pCon, pBueffel, eError);
return 0.;
}
iRet = MotorGetSoftPosition(self->pTwoTheta, pCon, &fTwoTheta);
if (!iRet) {
snprintf(pBueffel,sizeof(pBueffel)-1,
"ERROR: cannot read TwoTheta motor for monochromator %s\n",
self->name);
SCWrite(pCon, pBueffel, eError);
return 0.;
}
/* check for sync */
fVal = fTwoTheta - 2 * fTheta;
if (fVal < 0.)
fVal = -fVal;
if (fVal > 0.01) {
snprintf(pBueffel,sizeof(pBueffel)-1, "WARNING: monochromator %s out of sync by %f\n",
self->name, fVal);
SCWrite(pCon, pBueffel, eWarning);
}
/* calculate wavelength from angles */
dTheta = (double) fTheta;
dTheta = dTheta * (1.0 / RD);
fVal = 2.0 * (double) ObVal(self->pParams, LATD);
fVal = fVal * sin(dTheta) * ObVal(self->pParams, SS);
return fVal;
}
/*--------------------------------------------------------------------------*/
int GetMonoPositions(pSicsSelector self, SConnection * pCon,
float *fTh, float *f2TH, float *fB1, float *fB2)
{
int iRet;
char pBueffel[512];
iRet = MotorGetSoftPosition(self->pTheta, pCon, fTh);
if (!iRet) {
snprintf(pBueffel,sizeof(pBueffel)-1,
"ERROR: cannot read Theta motor for monochromator %s\n",
self->name);
SCWrite(pCon, pBueffel, eError);
return 0.;
}
iRet = MotorGetSoftPosition(self->pTwoTheta, pCon, f2TH);
if (!iRet) {
snprintf(pBueffel,sizeof(pBueffel)-1,
"ERROR: cannot read TwoTheta motor for monochromator %s\n",
self->name);
SCWrite(pCon, pBueffel, eError);
return 0.;
}
if (self->pBend1) {
iRet = MotorGetSoftPosition(self->pBend1, pCon, fB1);
if (!iRet) {
snprintf(pBueffel,sizeof(pBueffel)-1,
"ERROR: cannot read vertical bender motor for monochromator %s\n",
self->name);
SCWrite(pCon, pBueffel, eError);
return 0.;
}
}
if (self->pBend2) {
iRet = MotorGetSoftPosition(self->pBend2, pCon, fB2);
if (!iRet) {
snprintf(pBueffel,sizeof(pBueffel)-1,
"ERROR: cannot read horizontal bender motor for monochromator %s\n",
self->name);
SCWrite(pCon, pBueffel, eError);
return 0.;
}
}
return 1;
}