1170 lines
30 KiB
C
1170 lines
30 KiB
C
/*--------------------------------------------------------------------------
|
|
Yet another driver for a Astrium == Dornier velocity selector. This one
|
|
is for the new control software with the TCP-server installed. Actually
|
|
this is only valid for the modified protocoll as implemented at ANSTO.
|
|
|
|
As the motor for the tilt is included with the Astrium package, this code
|
|
also has to implement a motor driver for that motor.
|
|
|
|
As even the Tcp version of the Astrium Control Software is slow in responding,
|
|
a state machine has beem implemented for status request. There are only two
|
|
states: status request sent (WAITING) or status message processed (READY).
|
|
The idea is that getStatus sends a request and returns VSACCEL. It then
|
|
tests for data availability. If no data is available, VSACCEL is returned,
|
|
else the data is processed. This is done in order not to make SICS
|
|
unresponsive for seconds while driving the velocity selector. This scheme
|
|
implies that all other functions must take care of the state the connection
|
|
is in and possibly read the connection free before doing their work.
|
|
|
|
There is another efficiency feauture in place which causes status messages
|
|
younger then three times timeout to be reused. The VS does everything very
|
|
slowly, thus this is good enough.
|
|
|
|
The new command set for the VS hides this, but it is a known fact that the
|
|
VS has two modes of operation: above a certain threshold he can be normally
|
|
driven. Below that threshold (~3000 rpm) it must be started. Starting does
|
|
sometimes fail. This code allows the VS half an hour to start before flagging
|
|
an error.
|
|
|
|
copyright: see file COPYRIGHT
|
|
|
|
Mark Koennecke, December 2005
|
|
----------------------------------------------------------------------------*/
|
|
#include <sics.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include <tcl.h>
|
|
#include <time.h>
|
|
#include <modriv.h>
|
|
#include <motor.h>
|
|
#include <velo.h>
|
|
#include <velodriv.h>
|
|
#include <evcontroller.h>
|
|
#include <evdriver.i>
|
|
#include <velo.i>
|
|
#include <rs232controller.h>
|
|
#include <stringdict.h>
|
|
#include <stptok.h>
|
|
|
|
|
|
#define RPMALIFE 3100
|
|
|
|
/* defines for the communication state*/
|
|
#define READY 0
|
|
#define WAITING 1
|
|
|
|
|
|
/* error codes */
|
|
#define BADREPLY -17501
|
|
#define BADACCEPT -17502
|
|
#define SELTOFAST -17503
|
|
#define FAILEDSTART -17504
|
|
|
|
extern char *trim(char *str);
|
|
#define ABS(x) (x < 0 ? -(x) : (x))
|
|
/*----------------------------- The private data structure ---------------*/
|
|
typedef struct {
|
|
prs232 controller;
|
|
int iLastError;
|
|
float fTarget;
|
|
int comState;
|
|
int timeout;
|
|
pStringDict status;
|
|
time_t requestTimeout;
|
|
time_t statusAge;
|
|
time_t driveStart;
|
|
int debug;
|
|
char user[132];
|
|
char pword[132];
|
|
} TcpDornier, *pTcpDornier;
|
|
/*------------------------------------------------------------------------
|
|
Some utility stuff to parse the response from the velocity selector.
|
|
Any response contains the whole status message. Thus we can use any
|
|
response to update the status.
|
|
----------------------------------------------------------------------------*/
|
|
static void addAstriumPar(char *token, pStringDict target)
|
|
{
|
|
char name[80], value[80];
|
|
char *pos = NULL;
|
|
|
|
pos = strstr(token, "=");
|
|
if (pos != NULL) {
|
|
memset(name, 0, 79);
|
|
strncpy(name, token, pos - token);
|
|
strcpy(value, pos + 1);
|
|
}
|
|
if (StringDictExists(target, trim(name))) {
|
|
StringDictUpdate(target, trim(name), trim(value));
|
|
} else {
|
|
StringDictAddPair(target, trim(name), trim(value));
|
|
}
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
static int parseTcpDornierStatus(char *statusText, pStringDict target)
|
|
{
|
|
char *pos = NULL;
|
|
char token[80];
|
|
|
|
/*
|
|
test if this is a valid message
|
|
*/
|
|
if (strstr(statusText, "#SOS#") == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
extract the command status code
|
|
*/
|
|
pos = stptok(statusText + 5, token, 79, "#");
|
|
if (pos == NULL) {
|
|
return 0;
|
|
}
|
|
if (StringDictExists(target, "commandstatus") == 1) {
|
|
StringDictUpdate(target, "commandstatus", trim(token));
|
|
} else {
|
|
StringDictAddPair(target, "commandstatus", trim(token));
|
|
}
|
|
|
|
/*
|
|
extract all the others
|
|
*/
|
|
while ((pos = stptok(pos, token, 79, "#")) != NULL) {
|
|
addAstriumPar(token, target);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
static int readAstriumReply(prs232 controller, char *buffer, int buflen,
|
|
int timeout)
|
|
{
|
|
int status;
|
|
int bytesRead = 0;
|
|
time_t endTime;
|
|
char *pos = NULL;
|
|
|
|
endTime = time(NULL) + timeout;
|
|
memset(buffer, 0, buflen * sizeof(char));
|
|
while (time(NULL) < endTime) {
|
|
if (availableRS232(controller)) {
|
|
bytesRead = recv(controller->pSock->sockid, buffer + bytesRead,
|
|
buflen - bytesRead, 0);
|
|
if (bytesRead < 0) {
|
|
return BADREAD;
|
|
}
|
|
/*
|
|
as of december-9-2005 the Astrium protocoll has no proper
|
|
terminator. I check here for the last data entry which
|
|
happens to be BCUNN. The # after that terminates the
|
|
message. This algoritjm may need to be modified when
|
|
the protocoll changes.
|
|
*/
|
|
|
|
pos = strstr(buffer, "BCUUN");
|
|
if (pos != NULL) {
|
|
pos = strstr(pos + 1, "#");
|
|
if (pos != NULL) {
|
|
return 1;
|
|
}
|
|
}
|
|
} else {
|
|
SicsWait(1);
|
|
}
|
|
}
|
|
return TIMEOUT;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
static int readAndDecodeReply(pTcpDornier pDorn)
|
|
{
|
|
int status;
|
|
char buffer[1024];
|
|
|
|
status =
|
|
readAstriumReply(pDorn->controller, buffer, 1023, pDorn->timeout);
|
|
if (pDorn->debug > 0) {
|
|
printf("Read status = %d, Read data = %s\n", status, buffer);
|
|
}
|
|
if (status != 1) {
|
|
pDorn->iLastError = status;
|
|
return 0;
|
|
}
|
|
pDorn->comState = READY;
|
|
pDorn->statusAge = time(NULL);
|
|
status = parseTcpDornierStatus(buffer, pDorn->status);
|
|
if (status != 1) {
|
|
pDorn->iLastError = BADREPLY;
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static int readAstriumValue(pTcpDornier pDorn, char *key, float *value)
|
|
{
|
|
int status;
|
|
char sValue[80];
|
|
|
|
status = readAndDecodeReply(pDorn);
|
|
if (status != 1) {
|
|
return status;
|
|
}
|
|
status = StringDictGet(pDorn->status, key, sValue, 79);
|
|
if (status != 1) {
|
|
pDorn->iLastError = BADREPLY;
|
|
return 0;
|
|
}
|
|
sscanf(sValue, "%f", value);
|
|
return 1;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static int AstriumConnect(pTcpDornier pDorn)
|
|
{
|
|
int status;
|
|
char buffer[256];
|
|
|
|
status = initRS232(pDorn->controller);
|
|
if (status != 1) {
|
|
return 0;
|
|
}
|
|
status = readRS232UntilWord(pDorn->controller, buffer, 255, "ID");
|
|
if (status != 1) {
|
|
return 0;
|
|
}
|
|
snprintf(buffer, 255, "user:%s", pDorn->user);
|
|
status = writeRS232(pDorn->controller, buffer, strlen(buffer));
|
|
if (status != 1) {
|
|
return 0;
|
|
}
|
|
status = readRS232UntilWord(pDorn->controller, buffer, 255, "password");
|
|
if (status != 1) {
|
|
return 0;
|
|
}
|
|
snprintf(buffer, 255, "password:%s", pDorn->pword);
|
|
status = writeRS232(pDorn->controller, buffer, strlen(buffer));
|
|
if (status != 1) {
|
|
return 0;
|
|
}
|
|
status = readRS232UntilWord(pDorn->controller, buffer, 255, "Hello");
|
|
if (status != 1) {
|
|
return 0;
|
|
}
|
|
pDorn->comState = READY;
|
|
return 1;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static float readSpeed(pStringDict dict)
|
|
{
|
|
char value[80];
|
|
float fPos;
|
|
|
|
/*
|
|
The dornier has two speed ranges: Normally it is ASPEED but under
|
|
100 it is SSPEED
|
|
*/
|
|
StringDictGet(dict, "ASPEED", value, 79);
|
|
sscanf(value, "%f", &fPos);
|
|
if (fPos < 20.) {
|
|
StringDictGet(dict, "SSPEED", value, 79);
|
|
sscanf(value, "%f", &fPos);
|
|
|
|
}
|
|
return fPos;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
static int GetTcpDornierPos(pVelSelDriv self, float *fPos)
|
|
{
|
|
pTcpDornier pDorn = NULL;
|
|
int status;
|
|
|
|
assert(self);
|
|
pDorn = (pTcpDornier) self->pPrivate;
|
|
|
|
/*
|
|
with astrium replying so slowly, we should not be bothered to get
|
|
the latest and greatest but rather be efficient in the case of frequent
|
|
requests in a short time, such as after driving. statusAge is set in
|
|
readAndDecode when succcessful.
|
|
*/
|
|
if (time(NULL) > pDorn->statusAge + 3 * pDorn->timeout) {
|
|
if (pDorn->comState == READY) {
|
|
status = writeRS232(pDorn->controller, "#SOS#STATE ", 11);
|
|
pDorn->comState = WAITING;
|
|
if (status != 1) {
|
|
pDorn->iLastError = status;
|
|
return 0;
|
|
}
|
|
}
|
|
if (readAndDecodeReply(pDorn) != 1) {
|
|
return 0;
|
|
}
|
|
}
|
|
*fPos = readSpeed(pDorn->status);
|
|
return 1;
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
static int TcpDornierHalt(pVelSelDriv self)
|
|
{
|
|
pTcpDornier pDorn = NULL;
|
|
int status;
|
|
|
|
assert(self);
|
|
pDorn = (pTcpDornier) self->pPrivate;
|
|
pDorn->fTarget = .0;
|
|
|
|
if (pDorn->comState == WAITING) {
|
|
readAndDecodeReply(pDorn);
|
|
}
|
|
status = writeRS232(pDorn->controller, "#SOS#BRAKE ", 11);
|
|
pDorn->comState = WAITING;
|
|
if (status != 1) {
|
|
pDorn->iLastError = status;
|
|
return 0;
|
|
}
|
|
readAndDecodeReply(pDorn);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
static int TcpDornierRun(pVelSelDriv self, float fVal)
|
|
{
|
|
int status;
|
|
char pCommand[50], pAnswer[50];
|
|
pTcpDornier pDorn = NULL;
|
|
|
|
assert(self);
|
|
pDorn = (pTcpDornier) self->pPrivate;
|
|
|
|
if (pDorn->comState == WAITING) {
|
|
readAndDecodeReply(pDorn);
|
|
}
|
|
pDorn->comState = READY;
|
|
/*
|
|
a requested value of 0 or very little means to stop
|
|
*/
|
|
if (fVal < self->fTolerance) {
|
|
pDorn->driveStart = time(NULL);
|
|
return TcpDornierHalt(self);
|
|
}
|
|
/* This is the normal logic: new value */
|
|
snprintf(pCommand, 49, "#SOS#SPEED %5d", (int) fVal);
|
|
status = writeRS232(pDorn->controller, pCommand, strlen(pCommand));
|
|
if (status != 1) {
|
|
pDorn->iLastError = status;
|
|
return 0;
|
|
}
|
|
pDorn->comState = WAITING;
|
|
pDorn->driveStart = time(NULL);
|
|
status = readAndDecodeReply(pDorn);
|
|
if (status != 1) {
|
|
return 0;
|
|
}
|
|
|
|
StringDictGet(pDorn->status, "commandstatus", pAnswer, 49);
|
|
if (strstr(pAnswer, "ACCEPT") == NULL) {
|
|
pDorn->iLastError = BADACCEPT;
|
|
return 0;
|
|
}
|
|
pDorn->fTarget = fVal;
|
|
return 1;
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
static int TcpDornierError(pVelSelDriv self, int *iCode,
|
|
char *error, int iErrLen)
|
|
{
|
|
pTcpDornier pDorn = NULL;
|
|
|
|
assert(self);
|
|
pDorn = (pTcpDornier) self->pPrivate;
|
|
|
|
*iCode = pDorn->iLastError;
|
|
switch (pDorn->iLastError) {
|
|
case BADREPLY:
|
|
strncpy(error, "Velocity Selector sent invalid reply", iErrLen);
|
|
break;
|
|
case BADACCEPT:
|
|
strncpy(error, "VS refused command or speed out of range", iErrLen);
|
|
break;
|
|
case SELTOFAST:
|
|
strncpy(error, "Cannot drive tilt angle while selector is running",
|
|
iErrLen);
|
|
break;
|
|
case FAILEDSTART:
|
|
strncpy(error, "Failed to start velocitty selector", iErrLen);
|
|
break;
|
|
default:
|
|
getRS232Error(pDorn->iLastError, error, iErrLen);
|
|
break;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
static int TcpDornierFixIt(pVelSelDriv self, int iError)
|
|
{
|
|
pTcpDornier pDorn = NULL;
|
|
int status;
|
|
|
|
assert(self);
|
|
pDorn = (pTcpDornier) self->pPrivate;
|
|
|
|
switch (iError) {
|
|
case BADREPLY:
|
|
case TIMEOUT:
|
|
pDorn->comState = READY;
|
|
return VELOREDO;
|
|
break;
|
|
case BADACCEPT:
|
|
pDorn->comState = READY;
|
|
case FAILEDCONNECT:
|
|
case SELTOFAST:
|
|
case FAILEDSTART:
|
|
return VELOFAIL;
|
|
break;
|
|
default:
|
|
/*
|
|
these are mostly connection errors
|
|
Try to reconnect
|
|
*/
|
|
closeRS232(pDorn->controller);
|
|
status = AstriumConnect(pDorn);
|
|
pDorn->comState = READY;
|
|
if (status) {
|
|
return VELOREDO;
|
|
} else {
|
|
return VELOFAIL;
|
|
}
|
|
break;
|
|
}
|
|
return VELOFAIL;
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
static int TcpDornierStat(pVelSelDriv self, int *iCode, float *fCur)
|
|
{
|
|
pTcpDornier pDorn = NULL;
|
|
float fDelta;
|
|
static int count = 0;
|
|
int status;
|
|
|
|
assert(self);
|
|
pDorn = (pTcpDornier) self->pPrivate;
|
|
|
|
*iCode = ROTMOVE;
|
|
if (pDorn->comState == READY) {
|
|
status = writeRS232(pDorn->controller, "#SOS#STATE ", 11);
|
|
if (status != 1) {
|
|
pDorn->iLastError = status;
|
|
return VSFAIL;
|
|
}
|
|
pDorn->comState = WAITING;
|
|
pDorn->requestTimeout = time(NULL) + pDorn->timeout;
|
|
}
|
|
|
|
status = availableRS232(pDorn->controller);
|
|
if (status == 1) {
|
|
status = readAndDecodeReply(pDorn);
|
|
if (status != 1) {
|
|
return VSFAIL;
|
|
}
|
|
pDorn->comState = READY;
|
|
*fCur = readSpeed(pDorn->status);
|
|
fDelta = *fCur - pDorn->fTarget;
|
|
if (ABS(fDelta) < self->fTolerance) {
|
|
count++;
|
|
/*
|
|
we want at least three readings of the selector within
|
|
tolerance before we believe it arrived
|
|
*/
|
|
if (count > 3) {
|
|
return VSOK;
|
|
} else {
|
|
return VSACCEL;
|
|
}
|
|
} else {
|
|
count = 0;
|
|
/*
|
|
if the VS is still at low speed after half an hour we must
|
|
assume that it failed to start and has already had its three
|
|
times worth of start tries. We flag this now...
|
|
*/
|
|
if (time(NULL) > pDorn->driveStart + 30 * 60 && *fCur < RPMALIFE) {
|
|
pDorn->iLastError = FAILEDSTART;
|
|
return VSFAIL;
|
|
}
|
|
}
|
|
return VSACCEL;
|
|
} else {
|
|
if (time(NULL) > pDorn->requestTimeout) {
|
|
pDorn->iLastError = TIMEOUT;
|
|
return VSFAIL;
|
|
} else {
|
|
return VSACCEL;
|
|
}
|
|
}
|
|
|
|
return VELOOK;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static int TcpDornierText(pVelSelDriv self, char *pText, int iTextLen)
|
|
{
|
|
pTcpDornier pDorn = NULL;
|
|
char buffer[1024];
|
|
const char *name = NULL;
|
|
char value[80], entry[132];
|
|
int charUsed = 0, status;
|
|
|
|
|
|
assert(self);
|
|
pDorn = (pTcpDornier) self->pPrivate;
|
|
memset(pText, 0, iTextLen * sizeof(char));
|
|
if (time(NULL) > pDorn->statusAge + 3. * pDorn->timeout) {
|
|
if (pDorn->comState == READY) {
|
|
status = writeRS232(pDorn->controller, "#SOS#STATE ", 11);
|
|
pDorn->comState = WAITING;
|
|
if (status != 1) {
|
|
pDorn->iLastError = status;
|
|
return 0;
|
|
}
|
|
}
|
|
if (readAndDecodeReply(pDorn) != 1) {
|
|
pDorn->comState = READY;
|
|
return 0;
|
|
}
|
|
}
|
|
strcpy(buffer, "");
|
|
while ((name = StringDictGetNext(pDorn->status, value, 79)) != NULL) {
|
|
if (strstr(name, "commandstatus") == NULL) {
|
|
snprintf(entry, 131, "%s = %s\n", name, value);
|
|
if (charUsed + 132 < 1023) {
|
|
strcat(buffer, entry);
|
|
charUsed += strlen(entry);
|
|
}
|
|
}
|
|
}
|
|
strncpy(pText, buffer, iTextLen);
|
|
return 1;
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
static int TcpDornierLoss(pVelSelDriv self, float *fLoss)
|
|
{
|
|
pTcpDornier pDorn = NULL;
|
|
int status;
|
|
time_t endTime;
|
|
float speed, soll;
|
|
static int count = 0;
|
|
char value[80];
|
|
|
|
assert(self);
|
|
pDorn = (pTcpDornier) self->pPrivate;
|
|
|
|
if (pDorn->comState == WAITING) {
|
|
readAndDecodeReply(pDorn);
|
|
}
|
|
status = writeRS232(pDorn->controller, "#SOS#PLOSS ", 11);
|
|
if (status != 1) {
|
|
pDorn->iLastError = status;
|
|
return 0;
|
|
}
|
|
pDorn->comState = WAITING;
|
|
status = readAndDecodeReply(pDorn);
|
|
if (status != 1) {
|
|
return 0;
|
|
}
|
|
count = 0;
|
|
|
|
/*
|
|
loop until at speed again
|
|
*/
|
|
endTime = time(NULL) + 10 * 60;
|
|
while (time(NULL) < endTime) {
|
|
status = writeRS232(pDorn->controller, "#SOS#STATE ", 11);
|
|
if (status != 1) {
|
|
pDorn->iLastError = status;
|
|
return 0;
|
|
}
|
|
status = readAndDecodeReply(pDorn);
|
|
if (status != 1) {
|
|
return 0;
|
|
}
|
|
speed = readSpeed(pDorn->status);
|
|
StringDictGet(pDorn->status, "RSPEED", value, 79);
|
|
sscanf(value, "%f", &soll);
|
|
if (ABS(soll - speed) < self->fTolerance) {
|
|
count++;
|
|
if (count > 3) {
|
|
StringDictGet(pDorn->status, "PLOSS", value, 79);
|
|
sscanf(value, "%f", fLoss);
|
|
return 1;
|
|
}
|
|
} else {
|
|
count = 0;
|
|
}
|
|
}
|
|
pDorn->iLastError = TIMEOUT;
|
|
return 0;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static void TcpDornierKill(void *pData)
|
|
{
|
|
pTcpDornier pDorn = NULL;
|
|
|
|
pDorn = (pTcpDornier) pData;
|
|
assert(pDorn);
|
|
|
|
if (pDorn->controller != NULL) {
|
|
KillRS232(pDorn->controller);
|
|
}
|
|
if (pDorn->status != NULL) {
|
|
DeleteStringDict(pDorn->status);
|
|
}
|
|
|
|
free(pDorn);
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
static int TcpDornierInit(pVelSelDriv self, SConnection * pCon)
|
|
{
|
|
pTcpDornier pDorn = NULL;
|
|
int status;
|
|
|
|
assert(self);
|
|
pDorn = (pTcpDornier) self->pPrivate;
|
|
assert(pDorn);
|
|
|
|
status = AstriumConnect(pDorn);
|
|
if (status != 1) {
|
|
SCWrite(pCon,
|
|
"ERROR: failed to connect or login to Astrium velocity selector controller",
|
|
eError);
|
|
return 1;
|
|
}
|
|
|
|
status = writeRS232(pDorn->controller, "#SOS#STATE ", 11);
|
|
pDorn->comState = WAITING;
|
|
if (status != 1) {
|
|
SCWrite(pCon,
|
|
"ERROR: failed to write status request to controller", eError);
|
|
return 0;
|
|
}
|
|
status = readAndDecodeReply(pDorn);
|
|
if (status != 1) {
|
|
SCWrite(pCon,
|
|
"ERROR: failed to read and decode status request", eError);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*=========================================================================
|
|
Section with the motor driver code
|
|
==========================================================================*/
|
|
typedef struct __TcpAsMoDriv {
|
|
/* general motor driver interface
|
|
fields. REQUIRED!
|
|
*/
|
|
float fUpper; /* upper limit */
|
|
float fLower; /* lower limit */
|
|
char *name;
|
|
int (*GetPosition) (void *self, float *fPos);
|
|
int (*RunTo) (void *self, float fNewVal);
|
|
int (*GetStatus) (void *self);
|
|
void (*GetError) (void *self, int *iCode, char *buffer, int iBufLen);
|
|
int (*TryAndFixIt) (void *self, int iError, float fNew);
|
|
int (*Halt) (void *self);
|
|
int (*GetDriverPar) (void *self, char *name, float *value);
|
|
int (*SetDriverPar) (void *self, SConnection * pCon,
|
|
char *name, float newValue);
|
|
void (*ListDriverPar) (void *self, char *motorName, SConnection * pCon);
|
|
void (*KillPrivate) (void *self);
|
|
pTcpDornier master;
|
|
pVelSelDriv selector;
|
|
float tiltTarget;
|
|
float fTolerance;
|
|
} TcpAsMotorDriver, *pTcpAsMotorDriver;
|
|
/*-------------------------------------------------------------------------*/
|
|
static int TcpMotGetPosition(void *pData, float *fPos)
|
|
{
|
|
int status;
|
|
pTcpAsMotorDriver self = NULL;
|
|
char pAnswer[80];
|
|
|
|
self = (pTcpAsMotorDriver) pData;
|
|
assert(self != NULL);
|
|
|
|
/*
|
|
same as above for slow status reponses
|
|
*/
|
|
if (time(NULL) > self->master->statusAge + 3 * self->master->timeout) {
|
|
if (self->master->comState == READY) {
|
|
status = writeRS232(self->master->controller, "#SOS#STATE ", 11);
|
|
if (status != 1) {
|
|
self->master->comState = WAITING;
|
|
self->master->iLastError = status;
|
|
return 0;
|
|
}
|
|
}
|
|
status = readAstriumValue(self->master, "TTANG", fPos);
|
|
if (status != 1) {
|
|
return 0;
|
|
}
|
|
} else {
|
|
StringDictGet(self->master->status, "TTANG", pAnswer, 79);
|
|
sscanf(pAnswer, "%f", fPos);
|
|
return 1;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
static int TcpMotRunTo(void *pData, float newValue)
|
|
{
|
|
int status;
|
|
pTcpAsMotorDriver self = NULL;
|
|
float speed;
|
|
char command[80], pAnswer[80];
|
|
|
|
self = (pTcpAsMotorDriver) pData;
|
|
assert(self != NULL);
|
|
|
|
|
|
if (self->master->comState == WAITING) {
|
|
readAndDecodeReply(self->master);
|
|
}
|
|
|
|
/*
|
|
just another test to make sure that the selector is stopped before
|
|
driving that motor
|
|
*/
|
|
speed = readSpeed(self->master->status);
|
|
if (speed > 10) {
|
|
self->master->iLastError = SELTOFAST;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
send a command
|
|
*/
|
|
snprintf(command, 79, "#SOS#TTANGL %6.3f", newValue);
|
|
status = writeRS232(self->master->controller, command, strlen(command));
|
|
if (status != 1) {
|
|
self->master->iLastError = status;
|
|
return 0;
|
|
}
|
|
/*
|
|
check the reply
|
|
*/
|
|
status = readAndDecodeReply(self->master);
|
|
if (status != 1) {
|
|
return 0;
|
|
}
|
|
|
|
StringDictGet(self->master->status, "commandstatus", pAnswer, 49);
|
|
if (strstr(pAnswer, "ACCEPT") == NULL) {
|
|
self->master->iLastError = BADACCEPT;
|
|
return 0;
|
|
}
|
|
self->tiltTarget = newValue;
|
|
return 1;
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
static int TcpAsMotStat(void *pData)
|
|
{
|
|
pTcpAsMotorDriver self = NULL;
|
|
pTcpDornier pDorn = NULL;
|
|
float fDelta, value;
|
|
int status;
|
|
|
|
self = (pTcpAsMotorDriver) pData;
|
|
assert(self != NULL);
|
|
|
|
assert(self);
|
|
pDorn = self->master;
|
|
|
|
if (pDorn->comState == READY) {
|
|
status = writeRS232(pDorn->controller, "#SOS#STATE ", 11);
|
|
if (status != 1) {
|
|
pDorn->iLastError = status;
|
|
return HWFault;
|
|
}
|
|
pDorn->comState = WAITING;
|
|
pDorn->requestTimeout = time(NULL) + pDorn->timeout;
|
|
}
|
|
|
|
status = availableRS232(pDorn->controller);
|
|
if (status == 1) {
|
|
status = readAstriumValue(pDorn, "TTANG", &value);
|
|
if (status != 1) {
|
|
return HWFault;
|
|
}
|
|
pDorn->comState = READY;
|
|
fDelta = value - self->tiltTarget;
|
|
if (ABS(fDelta) < self->fTolerance) {
|
|
return HWIdle;
|
|
} else {
|
|
return HWBusy;
|
|
}
|
|
} else {
|
|
if (time(NULL) > pDorn->requestTimeout) {
|
|
pDorn->iLastError = TIMEOUT;
|
|
return HWFault;
|
|
} else {
|
|
return HWBusy;
|
|
}
|
|
}
|
|
return HWIdle;
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
static void TcpAsGetError(void *pData, int *iCode, char *error, int errLen)
|
|
{
|
|
pTcpAsMotorDriver self = NULL;
|
|
|
|
self = (pTcpAsMotorDriver) pData;
|
|
assert(self != NULL);
|
|
TcpDornierError(self->selector, iCode, error, errLen);
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
static int TcpAsFixit(void *pData, int code, float fnew)
|
|
{
|
|
pTcpAsMotorDriver self = NULL;
|
|
int status;
|
|
|
|
self = (pTcpAsMotorDriver) pData;
|
|
assert(self != NULL);
|
|
|
|
if (code == BADACCEPT) {
|
|
return MOTREDO;
|
|
}
|
|
|
|
status = TcpDornierFixIt(self->selector, code);
|
|
switch (status) {
|
|
case VELOFAIL:
|
|
return MOTFAIL;
|
|
break;
|
|
case VELOREDO:
|
|
return MOTREDO;
|
|
break;
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
return MOTFAIL;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
static int TcpAsMotHalt(void *pData)
|
|
{
|
|
/*
|
|
There is no command to halt the tilt motor!
|
|
*/
|
|
return 1;
|
|
}
|
|
|
|
/*================= creation code ========================================*/
|
|
pVelSelDriv VSCreateTcpDornierANSTO(char *name, Tcl_Interp * pTcl)
|
|
{
|
|
pVelSelDriv pNew = NULL;
|
|
pTcpDornier pDorn = NULL;
|
|
MotorDriver *pAstDriv = NULL;
|
|
char *pPtr = NULL;
|
|
char host[132];
|
|
int iVal, iRet, port;
|
|
|
|
/* the most likely error is the parameters specified are wrong!
|
|
So check this first. We''ll use Tcl's result for error reporting.
|
|
name is the name of an Tcl array which should hold the info
|
|
necessary
|
|
*/
|
|
|
|
/* allocate a TcpDornier structure */
|
|
pDorn = (pTcpDornier) malloc(sizeof(TcpDornier));
|
|
if (!pDorn) {
|
|
return NULL;
|
|
}
|
|
memset(pDorn, 0, sizeof(TcpDornier));
|
|
|
|
|
|
/* host name */
|
|
pPtr = (char *) Tcl_GetVar2(pTcl, name, "Host", TCL_GLOBAL_ONLY);
|
|
if (!pPtr) {
|
|
Tcl_AppendResult(pTcl, "ERROR: no hostname found in", name, NULL);
|
|
free(pDorn);
|
|
return NULL;
|
|
}
|
|
strncpy(host, pPtr, 131);
|
|
|
|
/* port number */
|
|
pPtr = (char *) Tcl_GetVar2(pTcl, name, "Port", TCL_GLOBAL_ONLY);
|
|
if (!pPtr) {
|
|
Tcl_AppendResult(pTcl, "ERROR: no port number found in", name, NULL);
|
|
free(pDorn);
|
|
return NULL;
|
|
}
|
|
iRet = Tcl_GetInt(pTcl, pPtr, &iVal);
|
|
if (iRet != TCL_OK) {
|
|
free(pDorn);
|
|
return NULL;
|
|
}
|
|
port = iVal;
|
|
pDorn->controller = createRS232(host, port);
|
|
pDorn->status = CreateStringDict();
|
|
if (pDorn->controller == NULL || pDorn->status == NULL) {
|
|
free(pDorn);
|
|
return NULL;
|
|
}
|
|
setRS232SendTerminator(pDorn->controller, "\r\n");
|
|
|
|
/*
|
|
username and password
|
|
*/
|
|
pPtr = (char *) Tcl_GetVar2(pTcl, name, "User", TCL_GLOBAL_ONLY);
|
|
if (!pPtr) {
|
|
strncpy(pDorn->user, "NVS", 131);
|
|
} else {
|
|
strncpy(pDorn->user, pPtr, 131);
|
|
}
|
|
pPtr = (char *) Tcl_GetVar2(pTcl, name, "Password", TCL_GLOBAL_ONLY);
|
|
if (!pPtr) {
|
|
strncpy(pDorn->pword, "NVS", 131);
|
|
} else {
|
|
strncpy(pDorn->pword, pPtr, 131);
|
|
}
|
|
|
|
/* time out. This one gets defaulted when not specified */
|
|
pPtr = (char *) Tcl_GetVar2(pTcl, name, "Timeout", TCL_GLOBAL_ONLY);
|
|
if (!pPtr) {
|
|
pDorn->timeout = 5;
|
|
} else {
|
|
iRet = Tcl_GetInt(pTcl, pPtr, &iVal);
|
|
if (iRet == TCL_OK) {
|
|
pDorn->timeout = iVal;
|
|
}
|
|
}
|
|
|
|
pPtr = (char *) Tcl_GetVar2(pTcl, name, "Debug", TCL_GLOBAL_ONLY);
|
|
if (pPtr != NULL) {
|
|
setRS232Debug(pDorn->controller, 10);
|
|
pDorn->debug = 1;
|
|
}
|
|
|
|
|
|
/* business as usual: allocate memory */
|
|
pNew = (pVelSelDriv) malloc(sizeof(VelSelDriv));
|
|
if (!pNew) {
|
|
return NULL;
|
|
}
|
|
|
|
/* zero the world */
|
|
memset(pNew, 0, sizeof(VelSelDriv));
|
|
pNew->pPrivate = pDorn;
|
|
|
|
/* initialise function pointers */
|
|
pNew->DeletePrivate = TcpDornierKill;
|
|
pNew->Halt = TcpDornierHalt;
|
|
pNew->GetError = TcpDornierError;
|
|
pNew->TryAndFixIt = TcpDornierFixIt;
|
|
pNew->GetRotation = GetTcpDornierPos;
|
|
pNew->SetRotation = TcpDornierRun;
|
|
pNew->GetStatus = TcpDornierStat;
|
|
pNew->GetDriverText = TcpDornierText;
|
|
pNew->GetLossCurrent = TcpDornierLoss;
|
|
pNew->Init = TcpDornierInit;
|
|
|
|
/* tolerance This one gets defaulted when not specified */
|
|
pPtr = (char *) Tcl_GetVar2(pTcl, name, "Tolerance", TCL_GLOBAL_ONLY);
|
|
if (!pPtr) {
|
|
pNew->fTolerance = 10.;
|
|
} else {
|
|
iRet = Tcl_GetInt(pTcl, pPtr, &iVal);
|
|
if (iRet != TCL_OK) {
|
|
pNew->fTolerance = (float) iVal;
|
|
}
|
|
}
|
|
|
|
|
|
/* done it */
|
|
return pNew;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static pTcpAsMotorDriver MakeAstriumMotor(pVelSelDriv sel,
|
|
pTcpDornier pDorn)
|
|
{
|
|
pTcpAsMotorDriver pNew = NULL;
|
|
|
|
pNew = (pTcpAsMotorDriver) malloc(sizeof(TcpAsMotorDriver));
|
|
if (pNew == NULL) {
|
|
return NULL;
|
|
}
|
|
memset(pNew, 0, sizeof(TcpAsMotorDriver));
|
|
pNew->GetPosition = TcpMotGetPosition;
|
|
pNew->RunTo = TcpMotRunTo;
|
|
pNew->GetStatus = TcpAsMotStat;
|
|
pNew->GetError = TcpAsGetError;
|
|
pNew->TryAndFixIt = TcpAsFixit;
|
|
pNew->Halt = TcpAsMotHalt;
|
|
pNew->selector = sel;
|
|
pNew->master = pDorn;
|
|
pNew->fTolerance = .1;
|
|
return (pTcpAsMotorDriver) pNew;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
extern pEVDriver MakeDummyVel(pVelSel pVel); /* in velo.c */
|
|
|
|
int VelSelTcpFactory(SConnection * pCon, SicsInterp * pSics, void *pData,
|
|
int argc, char *argv[])
|
|
{
|
|
pVelSelDriv pDriv = NULL;
|
|
pVelSel pNew = NULL;
|
|
pMotor pTilt = NULL;
|
|
char pBueffel[256];
|
|
Tcl_Interp *pT = NULL;
|
|
int iRet;
|
|
pEVDriver pMonDriv = NULL;
|
|
pTcpAsMotorDriver pAstDriv = NULL;
|
|
char *pPtr = NULL;
|
|
double d;
|
|
float limit;
|
|
|
|
assert(pCon);
|
|
assert(pSics);
|
|
|
|
/* minimum 3 arguments! */
|
|
if (argc < 3) {
|
|
SCWrite(pCon,
|
|
"ERROR: Insufficient number of arguments to VelSelFactory",
|
|
eError);
|
|
return 0;
|
|
}
|
|
|
|
/* first one is name */
|
|
strtolower(argv[1]);
|
|
|
|
/* second is the Tcl-array with the parameters
|
|
Create the velocity selector driver
|
|
*/
|
|
pDriv = VSCreateTcpDornierANSTO(argv[2], pSics->pTcl);
|
|
if (pDriv == NULL) {
|
|
SCWrite(pCon, "ERROR: failed to create velocity selector driver",
|
|
eError);
|
|
return 0;
|
|
}
|
|
pAstDriv = MakeAstriumMotor(pDriv, (pTcpDornier) pDriv->pPrivate);
|
|
if (pAstDriv == NULL) {
|
|
SCWrite(pCon, "ERROR: failed to create velocity selector motor driver",
|
|
eError);
|
|
return 0;
|
|
}
|
|
pTilt =
|
|
MotorInit("astriumdriver", "selectortilt", (MotorDriver *) pAstDriv);
|
|
if (pTilt == NULL) {
|
|
SCWrite(pCon, "ERROR: failed to create velocity selector motor",
|
|
eError);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
now initialize additional parameters for the motor
|
|
*/
|
|
pPtr = (char *) Tcl_GetVar2(pSics->pTcl, argv[2], "TiltTolerance",
|
|
TCL_GLOBAL_ONLY);
|
|
pAstDriv->fTolerance = .1;
|
|
if (pPtr != NULL) {
|
|
iRet = Tcl_GetDouble(pSics->pTcl, pPtr, &d);
|
|
if (iRet == TCL_OK) {
|
|
pAstDriv->fTolerance = (float) d;
|
|
}
|
|
}
|
|
|
|
limit = 10.;
|
|
pPtr =
|
|
(char *) Tcl_GetVar2(pSics->pTcl, argv[2], "TiltUpper",
|
|
TCL_GLOBAL_ONLY);
|
|
if (pPtr != NULL) {
|
|
iRet = Tcl_GetDouble(pSics->pTcl, pPtr, &d);
|
|
if (iRet == TCL_OK) {
|
|
limit = (float) d;
|
|
}
|
|
}
|
|
pAstDriv->fUpper = limit;
|
|
MotorSetPar(pTilt, pCon, "softupperlim", limit);
|
|
|
|
pPtr =
|
|
(char *) Tcl_GetVar2(pSics->pTcl, argv[2], "TiltLower",
|
|
TCL_GLOBAL_ONLY);
|
|
limit = -10.;
|
|
if (pPtr != NULL) {
|
|
iRet = Tcl_GetDouble(pSics->pTcl, pPtr, &d);
|
|
if (iRet == TCL_OK) {
|
|
limit = (float) d;
|
|
}
|
|
}
|
|
pAstDriv->fLower = limit;
|
|
MotorSetPar(pTilt, pCon, "softlowerlim", limit);
|
|
|
|
|
|
|
|
/* now initialise this and install it as command */
|
|
pNew = VSCreate(pTilt, pDriv);
|
|
if (!pNew) {
|
|
SCWrite(pCon, "ERROR: creating velocity selector, no memory", eError);
|
|
return 0;
|
|
}
|
|
iRet = pDriv->Init(pDriv, pCon);
|
|
if (!iRet) {
|
|
SCWrite(pCon, "ERROR: failed to initialize velocity selector", eError);
|
|
VSDestroy(pNew);
|
|
return 0;
|
|
}
|
|
pNew->pName = strdup(argv[1]);
|
|
iRet = AddCommand(pSics, argv[1], VelSelAction, VSDestroy, pNew);
|
|
if (!iRet) {
|
|
sprintf(pBueffel, "ERROR: duplicate command %s not created", argv[2]);
|
|
SCWrite(pCon, pBueffel, eError);
|
|
VSDestroy((void *) pNew);
|
|
return 0;
|
|
}
|
|
|
|
/* install the evcontroller bit of the velocity selector */
|
|
pMonDriv = MakeDummyVel(pNew);
|
|
if (!pMonDriv) {
|
|
RemoveCommand(pSics, argv[1]);
|
|
SCWrite(pCon, "ERROR: failed to create monitor for nvs", eError);
|
|
return 0;
|
|
}
|
|
pBueffel[0] = '\0';
|
|
strcpy(pBueffel, argv[1]);
|
|
strcat(pBueffel, "watch");
|
|
pNew->pMonitor = CreateEVController(pMonDriv, pBueffel, &iRet);
|
|
if (!pNew->pMonitor) {
|
|
DeleteEVDriver(pMonDriv); /* was missing M.Z. Jul 04 */
|
|
SCWrite(pCon, "ERROR: failed to create monitor for nvs", eError);
|
|
return 0;
|
|
}
|
|
iRet = AddCommand(pSics, pBueffel, EVControlWrapper,
|
|
NULL, pNew->pMonitor);
|
|
if (!iRet) {
|
|
sprintf(pBueffel, "ERROR: duplicate command %s not created", pBueffel);
|
|
RemoveCommand(pSics, argv[1]);
|
|
return 0;
|
|
}
|
|
EVRegisterController(FindEMON(pSics), pBueffel, pNew->pMonitor, pCon);
|
|
return iRet;
|
|
}
|