Files
sics/site_ansto/hardsup/nhq200util.c
Douglas Clowes 6d449014fc remove misleading comment
r1974 | dcl | 2007-05-22 08:15:56 +1000 (Tue, 22 May 2007) | 2 lines
2012-11-15 13:17:55 +11:00

652 lines
18 KiB
C
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*--------------------------------------------------------------------------
N H Q 2 0 0 U T I L
A few utility functions for dealing with a NHQ200 voltage controller
within the ANSTO setup: host === TCP/IP === MOXA === RS-232.
Mark Koennecke, Juli 1997
Mark Lesha, January 2006 (based on ITC4 code)
Douglas Clowes, December 2006 (based on LAKESHORE340 code)
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 <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <fortify.h>
#include <sics.h>
#include <modriv.h>
#include <nwatch.h>
#include <asyncqueue.h>
#include "nhq200util.h"
/* -------------------------------------------------------------------*/
static pAsyncProtocol NHQ_Protocol = NULL;
static int NHQ_Tx1(pAsyncProtocol p, void* ctx)
{
int iRet = 1;
pAsyncTxn myCmd = (pAsyncTxn) ctx;
assert(myCmd);
iRet = AsyncUnitWrite(myCmd->unit, &myCmd->out_buf[myCmd->out_idx], 1);
return iRet;
}
static int NHQ_Tx(pAsyncProtocol p, pAsyncTxn myCmd)
{
/*
* Set/reset command states for send/resend of command
*/
myCmd->txn_state = 0;
myCmd->out_idx = 0;
myCmd->inp_idx = 0;
myCmd->txn_status = ATX_ACTIVE;
return NHQ_Tx1(p, myCmd);
}
static int NHQ_Rx(pAsyncProtocol p, pAsyncTxn myCmd, int rxchar)
{
int iRet = 1;
switch (myCmd->txn_state) {
case 0: /* send with echo */
if (rxchar != myCmd->out_buf[myCmd->out_idx]) {
/* TODO: bad echo */
}
else if (rxchar == 0x0A &&
myCmd->out_idx > 0 &&
myCmd->out_buf[myCmd->out_idx - 1] == 0x0D) {
myCmd->inp_idx = 0;
myCmd->txn_state = 1;
/* TODO: end of line */
}
else if (myCmd->out_idx < myCmd->out_len) {
myCmd->out_idx++;
iRet = NHQ_Tx1(p, myCmd);
}
else {
/* TODO: out of data */
}
break;
case 1: /* receiving reply */
if (myCmd->inp_idx < myCmd->inp_len)
myCmd->inp_buf[myCmd->inp_idx++] = rxchar;
if (rxchar == 0x0D)
myCmd->txn_state = 2;
break;
case 2: /* received CR and looking for LF */
if (myCmd->inp_idx < myCmd->inp_len)
myCmd->inp_buf[myCmd->inp_idx++] = rxchar;
if (rxchar == 0x0A) {
/* end of line */
myCmd->txn_state = 3;
myCmd->inp_idx -= 2;
myCmd->inp_buf[myCmd->inp_idx] = '\0';
myCmd->txn_status = ATX_COMPLETE;
iRet = 0;
}
else
myCmd->txn_state = 1;
break;
}
if (iRet == 0) { /* end of command */
return AQU_POP_CMD;
}
return iRet;
}
static int NHQ_Ev(pAsyncProtocol p, pAsyncTxn myCmd, int event)
{
if (event == AQU_TIMEOUT) {
/* TODO: handle command timeout */
myCmd->txn_status = ATX_TIMEOUT;
return AQU_POP_CMD;
}
return AQU_POP_CMD;
}
static void NHQ_Notify(void* context, int event)
{
pNHQ200 self = (pNHQ200) context;
switch (event) {
case AQU_DISCONNECT:
if (self->transWait == 1) {
self->transWait = NHQ200__FAULT;
strcpy(self->pAns, "DISCONNECTED");
}
case AQU_RECONNECT:
do {
mkChannel* sock = AsyncUnitGetSocket(self->unit);
int flag = 1;
setsockopt(sock->sockid, /* socket affected */
IPPROTO_TCP, /* set option at TCP level */
TCP_NODELAY, /* name of option */
(char *) &flag, /* the cast is historical cruft */
sizeof(int)); /* length of option value */
return;
} while (0);
}
return;
}
static void parse_hash(pNHQ200 self, const char* resp, int resp_len)
{
int iSrc;
int iDst;
iSrc = 0;
iDst = 0;
while (iSrc < resp_len && resp[iSrc]) {
if (resp[iSrc] == ';') {
++iSrc;
break;
}
self->serial_number[iDst++] = resp[iSrc++];
}
self->serial_number[iDst] = '\0';
iDst = 0;
while (iSrc < resp_len && resp[iSrc]) {
if (resp[iSrc] == ';') {
++iSrc;
break;
}
self->software_version[iDst++] = resp[iSrc++];
}
self->software_version[iDst] = '\0';
iDst = 0;
while (iSrc < resp_len && resp[iSrc]) {
if (resp[iSrc] == ';') {
++iSrc;
break;
}
self->voltage_max[iDst++] = resp[iSrc++];
}
self->voltage_max[iDst] = '\0';
iDst = 0;
while (iSrc < resp_len && resp[iSrc]) {
if (resp[iSrc] == ';') {
++iSrc;
break;
}
self->current_max[iDst++] = resp[iSrc++];
}
self->current_max[iDst] = '\0';
/* TODO: convert voltage and current */
}
static void parse_Sx(pNHQ200 self, const char* resp, int resp_len)
{
memcpy(self->status_s, resp, resp_len);
if (resp_len > 0 && self->status_s[resp_len - 1] == ' ')
--resp_len;
self->status_s[resp_len] = '\0';
}
static void parse_Tx(pNHQ200 self, const char* resp, int resp_len)
{
memcpy(self->status_t, resp, resp_len);
self->status_t[resp_len] = '\0';
self->module_status = strtol(self->status_t, NULL, 10);
}
static void parse_Mx(pNHQ200 self, const char* resp, int resp_len)
{
self->vmax_percent = strtol(resp, NULL, 10);
}
static void parse_Nx(pNHQ200 self, const char* resp, int resp_len)
{
self->imax_percent = strtol(resp, NULL, 10);
}
static void parse_Vx(pNHQ200 self, const char* resp, int resp_len)
{
self->ramp_input = strtol(resp, NULL, 10);
}
#define STATE_HASH 1
#define STATE_SX 2
#define STATE_TX 3
#define STATE_MX 4
#define STATE_NX 5
#define STATE_VX 6
#define STATE_END 9
static int InitCallback(pAsyncTxn pTxn)
{
char cmd[20];
int cmd_len;
const char* resp = pTxn->inp_buf;
int resp_len = pTxn->inp_idx;
pNHQ200 self = (pNHQ200) pTxn->cntx;
if (pTxn->txn_status == ATX_TIMEOUT) {
self->iError = NHQ200__BADSET;
self->iState = 0;
}
else {
switch (self->iState) {
case 0: /* Initial */
cmd_len = snprintf(cmd, sizeof(cmd), "#");
AsyncUnitSendTxn(self->unit, cmd, cmd_len, InitCallback, self, 80);
self->iState = STATE_HASH;
break;
case STATE_HASH: /* # */
parse_hash(self, resp, resp_len);
cmd_len = snprintf(cmd, sizeof(cmd), "S%d", self->iControl);
AsyncUnitSendTxn(self->unit, cmd, cmd_len, InitCallback, self, 80);
self->iState = STATE_SX;
break;
case STATE_SX: /* Sx */
parse_Sx(self, resp, resp_len);
cmd_len = snprintf(cmd, sizeof(cmd), "T%d", self->iControl);
AsyncUnitSendTxn(self->unit, cmd, cmd_len, InitCallback, self, 80);
self->iState = STATE_TX;
break;
case STATE_TX: /* Tx */
parse_Tx(self, resp, resp_len);
cmd_len = snprintf(cmd, sizeof(cmd), "M%d", self->iControl);
AsyncUnitSendTxn(self->unit, cmd, cmd_len, InitCallback, self, 80);
self->iState = STATE_MX;
break;
case STATE_MX: /* Mx */
parse_Mx(self, resp, resp_len);
cmd_len = snprintf(cmd, sizeof(cmd), "N%d", self->iControl);
AsyncUnitSendTxn(self->unit, cmd, cmd_len, InitCallback, self, 80);
self->iState = STATE_NX;
break;
case STATE_NX: /* Nx */
parse_Nx(self, resp, resp_len);
cmd_len = snprintf(cmd, sizeof(cmd), "V%d", self->iControl);
AsyncUnitSendTxn(self->unit, cmd, cmd_len, InitCallback, self, 80);
self->iState = STATE_VX;
break;
case STATE_VX: /* Vx */
parse_Vx(self, resp, resp_len);
self->iState = STATE_END;
break;
case STATE_END:
break;
}
}
return 0;
}
static void NHQ_Init(pNHQ200 self)
{
self->iState = 0;
AsyncUnitSendTxn(self->unit, "", 0, InitCallback, self, 80);
}
/*
* \brief GetCallback is the callback for the get position/value command.
*/
static int GetCallback(pAsyncTxn pTxn)
{
int iRet;
float fRead;
const char* resp = pTxn->inp_buf;
pNHQ200 self = (pNHQ200) pTxn->cntx;
if (pTxn->txn_status == ATX_TIMEOUT) {
self->iError = NHQ200__BADREAD;
}
else {
iRet = sscanf(resp,"%g",&fRead);
if(iRet != 1) { // Not a number, probably an error response
self->iError = NHQ200__BADREAD;
}
else {
if (fRead < 0)
fRead = -fRead;
self->fValue = fRead;
}
}
self->iGetOut = 0;
return 0;
}
/*
* \brief TransCallback is the callback for the general command transaction.
*/
static int TransCallback(pAsyncTxn pTxn)
{
const char* resp = pTxn->inp_buf;
int resp_len = pTxn->inp_idx;
pNHQ200 self = (pNHQ200) pTxn->cntx;
if (pTxn->txn_status == ATX_TIMEOUT) {
self->transReply[0] = '\0';
self->transWait = -1;
}
else {
memcpy(self->transReply, resp, resp_len);
self->transReply[resp_len] = '\0';
self->transWait = 0;
}
return 0;
}
/*------------------------------------------------------------------------*/
int transactNHQ200(pNHQ200 self, void *send, int sendLen,
void *reply, int replyLen)
{
assert(self);
self->transReply = reply;
self->transWait = 1;
AsyncUnitSendTxn(self->unit,
send, sendLen,
TransCallback, self, replyLen);
while (self->transWait == 1)
TaskYield(pServ->pTasker);
if (self->transWait < 0)
return self->transWait;
return 1;
}
/*------------------------------------------------------------------------*/
int NHQ200_Check_Status(pNHQ200 self)
/* Can be called to check for correct operation of the NHQ200 */
{
int iRet, iRetry, busy, notbusy;
char pCommand[20];
char pReply[132];
/* Check the busy status.
* While busy, wait but not too long - set an upper limit of about 100
* queries, should translate to about 1 or 2 seconds which is enough time for
* any commands to complete. Since we don't issue any time-consuming commands,
* this shouldn't be necessary anyway.
* Register a comms failure if a not-busy response isn't able to be received.
*/
iRetry=0;
printf("Checking status...");
do
{
sprintf(pCommand,"S%d", self->iControl);
iRet=AsyncUnitTransact(self->unit, pCommand, strlen(pCommand), pReply, 79);
if (iRet <= 0)
{
printf("Comms error!\n");
return iRet; // Comms problem
}
sprintf(pCommand,"S%d=ON", self->iControl);
busy=(strncmp(pReply,pCommand,5) != 0);
notbusy=!busy;
if (notbusy)
{
printf("Status OK.\n");
return 1; // Lakeshore 340 is ready to accept command
}
} while((++iRetry<100)&&busy);
/* If we fell out of the loop, the Lakeshore 340 is either still busy */
/* or some bad response was received, log the response. */
sprintf(self->pAns,"BUSY response=%s",pReply);
printf("Busy or bad response received!\n");
return NHQ200__BADREAD;
}
/* Operations common to both Open and Config functions */
static int NHQ200_Setup(pNHQ200 self, int iControl)
{
if (!self)
return NHQ200__BADCOM;
return 1; /* Success */
}
int NHQ200_Open(pNHQ200 *pData, char *pName, int iSensor, int iCTRL, int iMode)
{
int iRet = 1;
mkChannel* sock = NULL;
pNHQ200 self = NULL;
self = (pNHQ200)malloc(sizeof(NHQ200));
if(self == NULL)
{
return NHQ200__BADMALLOC;
}
memset(self, 0, sizeof(NHQ200));
*pData = self;
self->iControl = iCTRL;
self->iRead = iSensor;
self->iReadOnly = iMode;
/* The NHQ200 doesn't require divisors or multipliers
and they are always forced to 1.0 */
self->fDiv = 1.0;
self->fMult = 1.0;
if (AsyncUnitCreate(pName, &self->unit) == 0) {
return NHQ200__NONHQ200;
}
AsyncUnitSetNotify(self->unit, self, NHQ_Notify);
sock = AsyncUnitGetSocket(self->unit);
if (sock) {
int flag = 1;
iRet = setsockopt(sock->sockid, /* socket affected */
IPPROTO_TCP, /* set option at TCP level */
TCP_NODELAY, /* name of option */
(char *) &flag, /* the cast is historical cruft */
sizeof(int)); /* length of option value */
if (iRet < 0)
return NHQ200__BADCOM;
}
else
return NHQ200__BADCOM;
NHQ_Init(self);
return iRet;
}
/*--------------------------------------------------------------------------*/
void NHQ200_Close(pNHQ200 *pData)
{
pNHQ200 self;
self = *pData;
if (!self)
return; // Just in case
return;
}
/*--------------------------------------------------------------------------*/
int NHQ200_Config(pNHQ200 *pData, int iTmo, int iRead, int iControl,
float fDiv,float fMult)
{
pNHQ200 self;
self = *pData;
return NHQ200_Setup(self, iControl);
}
/* --------------------------------------------------------------------------*/
int NHQ200_Send(pNHQ200 *pData, char *pCommand, char *pReply, int iLen)
{
int iRet;
int commandlen;
pNHQ200 self;
self = *pData;
/* Send command direct to the NHQ200 */
commandlen=strlen(pCommand);
iRet=AsyncUnitTransact(self->unit, pCommand, commandlen, pReply, iLen);
return iRet;
}
/*--------------------------------------------------------------------------*/
int NHQ200_Read(pNHQ200 *pData, float *fVal)
{
char pCommand[20];
int iRet;
pNHQ200 self;
self = *pData;
/* for the NHQ200 there are two units available */
sprintf(pCommand,"U%d", self->iControl);
if (self->iGetOut == 0) {
struct timeval tv_this;
gettimeofday(&tv_this, NULL);
if ((tv_this.tv_sec - self->tv_last.tv_sec) > 0) {
AsyncUnitSendTxn(self->unit,
pCommand, 2,
GetCallback, self, 132);
self->iGetOut = 1;
self->tv_last = tv_this;
}
}
while (self->iGetOut) {
struct timeval tv_this;
gettimeofday(&tv_this, NULL);
if ((tv_this.tv_sec - self->tv_last.tv_sec) > 1)
break;
TaskYield(pServ->pTasker);
}
*fVal = self->fValue;
iRet = 1;
return iRet;
}
/* -------------------------------------------------------------------------*/
int NHQ200_Set(pNHQ200 *pData, float fVal)
{
char pCommand[20], pCommandRead[20], pReply[132], pCommandGo[20];
int iRet;
const float fPrecision = 0.1;
float fDelta, fRead;
pNHQ200 self;
self = *pData;
if(self->iReadOnly)
{
return NHQ200__READONLY;
}
/* command to set the voltage */
sprintf(pCommand,"D%d=%d", self->iControl, (int) (fVal + 0.5));
/* command to read back and check the set value */
sprintf(pCommandRead,"D%d", self->iControl);
/* send Dn=nnn command, we get a blank line response */
iRet = AsyncUnitTransact(self->unit,pCommand,strlen(pCommand),pReply,131);
if (iRet <= 0)
return iRet;
/* read the set value again using the Dn command */
iRet = AsyncUnitTransact(self->unit,pCommandRead,strlen(pCommandRead),pReply,131);
if (iRet <= 0)
return iRet;
printf("D%d: Response %d chars: '%s'\n",self->iControl, iRet, pReply);
/* Convert the value read back. */
if(sscanf(pReply,"%g",&fRead)!=1)
return NHQ200__BADREAD;
printf(" Parsed response OK, value=%g\n",fRead);
/* check the value read back */
printf(" Setpoint=%g actual=%g\n",fVal,fRead);
fDelta = fRead - fVal;
if(fDelta < 0)
fDelta = -fDelta;
printf(" delta=%g precision=%g\n",fDelta,fPrecision);
if(fDelta < fPrecision)
{
sprintf(pCommandGo, "G%d", self->iControl);
iRet = AsyncUnitTransact(self->unit,pCommandGo,strlen(pCommandGo),pReply,131);
if (iRet <= 0)
return iRet;
printf("G%d: Response %d chars: '%s'\n",self->iControl, iRet, pReply);
printf("SET OK, checking status and returning.\n");
return 1;
}
printf("SET failed!\n");
return NHQ200__BADSET;
}
/* -------------------------------------------------------------------------*/
void NHQ200_ErrorTxt(pNHQ200 *pData,int iCode, char *pError, int iLen)
{
char pBueffel[512];
pNHQ200 self;
self = *pData;
switch(iCode)
{
case NHQ200__BADCOM:
sprintf(pBueffel,"NHQ200: Invalid command or offline, got %s",
self->pAns);
strncpy(pError,pBueffel,iLen);
break;
case NHQ200__BADPAR:
strncpy(pError,"NHQ200: Invalid parameter specified",iLen);
break;
case NHQ200__BADMALLOC:
strncpy(pError,"NHQ200: Error allocating memory in NHQ200",iLen);
break;
case NHQ200__BADREAD:
strncpy(pError,"NHQ200: Badly formatted answer",iLen);
break;
case NHQ200__BADSET:
strncpy(pError,"NHQ200: Failed to write new set value to NHQ200",iLen);
break;
case NHQ200__FAULT: // Covers various NHQ200 self-diagnosed fault conditions
sprintf(pBueffel,"NHQ200: Internal fault: %s",self->pAns);
strncpy(pError,pBueffel,iLen);
break;
case NHQ200__NONHQ200:
sprintf(pBueffel,"NHQ200: Unit not found: %s",self->pAns);
strncpy(pError,pBueffel,iLen);
break;
default:
snprintf(pError, iLen, "NHQ200: Unknown Error: %d", iCode);
break;
}
}
void NHQ200InitProtocol(SicsInterp *pSics) {
if (NHQ_Protocol == NULL) {
NHQ_Protocol = AsyncProtocolCreate(pSics, "NHQ200", NULL, NULL);
NHQ_Protocol->sendCommand = NHQ_Tx;
NHQ_Protocol->handleInput = NHQ_Rx;
NHQ_Protocol->handleEvent = NHQ_Ev;
NHQ_Protocol->prepareTxn = NULL;
NHQ_Protocol->killPrivate = NULL;
}
}