Implements a protocol handler for the protek 608 multimeters which just allows us to read the display. It reports all elements of the display including the bar graph, it does not provide remote control of the multimeter. The protocol handler broadcasts a warning to all clients if the auto-off function is enabled. sct_rfamp.c This is a protocol handler for the Mirrortron 35V 7A AC Generator (ANSFR-83B). sinqhttpprot.c Copied the PSI script context http protocol handler. sct_orhvpsprot.c Ordela high voltage power supply protocol handler now catches unknown commands. sct_eurotherm_2000.tcl Eurotherm controller for the kowari load frame by Douglas Clowes. sct_lakeshore_3xx.tcl Latest update from Arndt. The two control loops are now independent, settletime and tolerance now work properly. common_instrument_dictionary.tcl Make instrument/status saveable. sct_orhvps_common.tcl Provides voltage ramping and implements the dhv1 command for the Ordela HVPS via the sct_orhpsprot.c protocol handler. hmm_configuration_common_1.tcl Adds new "histmem clockscale" subcommand to get and set the clock scale from the fat_clock_scale FAT parameter. You can now upload the FAT FRAME_BUFFER and FRAME_DUTYCYCLE parameters to the histogram memory. The veto commands are now "histmem veto on" and "histmem veto off". hmm_object.tcl The axis order for the histmem object has been restore to t,y,x sct_positmotor_common.tcl Code has been simplified. nxscripts_common_1.tcl Removed obsolete ::nexus::data function. TOF axis now correctly report time_of_flight instead of "time". plc_common_1.tcl Make PLC info saveable. scan_common_1.tcl SICS-385 The scan command should check the final scan variable value against he soft upper and lower limits, not against the hard limits. Make sure that the scan variable axis is saved. platypus, kowari, quokka hmm_configuration.tcl Use the HOR and VER entries in the new histmem_axes hash to select the horizontal and vertical axes for the histmem. kowari motor_configuration.tcl secondary_slit_configuration.tcl Flatten slits motor structure to match old layout in data files. quokka commands.tcl SICS-380 EApPosYmm -> EApPosY quokka detector.tcl Use new script context controller for Ordela HVPS quokka hmm_configuration.tcl Set detector height to 5.08*192 the same as the width quokka motor_configuration.tcl Code cleanup quokka positmotor_configuration.tcl Use new positmotor code. quokka aperture_configuration.tcl Added attenuation factor column to AttRotLookupTable quokka parameters.tcl SICS-380 Refactor nexus, remove redundant parameters. site_ansto.c Added the following protocols, Httpl, Protek608, aand RFAmp. scriptcontext.c SICS-386 SctActionHandler: set "send" string to NULL when a chain of scripts completes with state=idle. It turns out that if none of the scripts in the "read chain" call [sct send] each time the chain is executed, then SICS will hammer the device with calls to AsconWrite(). This can be avoided if SctActionHandler sets the 'send' string to NULL before "goto finish" in the idle state. This will be safer and still let you have chains with multiple [sct send] and read scripts. asyncprotocol.c Fix platypus memory leak. devser.c SICS-387 Started adding code to pass signals on to script context drivers. ascon.c AsconTask(): Make sure we return to the AsconIdle state when sending a command which expect no response, also only reconnect if there is a Timeout when there has been an error. r2888 | ffr | 2010-04-19 14:04:41 +1000 (Mon, 19 Apr 2010) | 90 lines
471 lines
13 KiB
C
471 lines
13 KiB
C
/*
|
|
@file sct_protek.c
|
|
@brief Protocol handler for Protek 608 multimeter
|
|
|
|
Reads the display bitmap and generates an ASCII string similar to the
|
|
following,
|
|
SD3:0.|SD4:1|SD5: |DUTY2:0|HOLD2:0|RANGE2:0|ZD2:0|DC2:0|SIGN2:0|AC2:0|CONT2:0|PW1:0|X1:0|B1:1|B0:1|B16:0|B8:0|B4:0|B2:0|DC1:1|SIGN1:1|AC1:0|LOBAT:0|MD5:0.|MD4:0|MD3:0|X2:0|X3:0|PLUS:0|MINUS:0|RS232C:1|AUTOOFF:0|X4:0|PULSE:0|STORE:0|AVG:0|MINUSPEAK:0|MIN:0|RECALL:0|REL:0|PLUSPEAK:0|MAX:0|GO/NG:0|REF:0|MINUSPER:0|PLUSPER:0|MD2:0|MD1:2|s1:0|DEGF1:0|Hz1:0|X5:0|F1:0|A1:0|OHM1:0|X6:0|DEGC1:0|S1:0|V1:1|k1:0|n1:0|m1:0|u1:0|M1:0|B8K:0|B16K:0|X7:0|X8:0|B4K:0|B2K:0|B1K:0|B512:0|B32:0|B64:0|B128:0|B256:0|M2:1|G2:0|m2:0|PERCENT2:0|k2:0|OHM2:1|V2:0|dBm2:0|X9:0|Hz2:0|A2:0|DEGK2:0|SD1:0|SD2:5|ROTSWITCH:0
|
|
|
|
NOTE: Multimeter must be connected via a moxa box
|
|
|
|
Author: Ferdi Franceschini (ffr@ansto.gov.au)
|
|
TODO The letters I, S, and O are currently being returned as 1, 5, and 0.
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <ascon.h>
|
|
#include <ascon.i>
|
|
#include <dynstring.h>
|
|
|
|
#define ERRLEN 128
|
|
#define CMDLEN 12
|
|
#define LASTBYTENUM 42
|
|
|
|
/* @brief Returns '1' if bit is set otherwise '0' */
|
|
#define GETBIT(MASK, BYTE) (MASK & BYTE ? '1' : '0')
|
|
|
|
/* @brief Interprets BYTE and returns a string of fields and values, eg "F1:v1|F2:v2" */
|
|
#define SETFIELD(L1, L2, L3, L4, BYTE) (sprintf(field, "%s:%c|%s:%c|%s:%c|%s:%c", L1, GETBIT(0x8, BYTE), L2, GETBIT(0x4, BYTE), L3, GETBIT(0x2, BYTE), L4, GETBIT(0x1, BYTE)))
|
|
|
|
struct protekData {
|
|
unsigned char defa, xcgb;
|
|
int first;
|
|
};
|
|
|
|
void protKill(void *private)
|
|
{
|
|
struct protekData *data = private;
|
|
free(data);
|
|
}
|
|
|
|
/*
|
|
* @brief Decodes a seven segment display bit pattern into an ASCII character
|
|
* The point bit, x in xCGB, is ignored in an 8 segment bit pattern.
|
|
*
|
|
* @param DEFA bit pattern for the left half of a seven segment display
|
|
* @param xCGB bit pattern fot the right half of the display, x is the point bit for an 8 segment pattern.
|
|
* @return ASCII numeral '0' - '9', ' ' for a blank character, 'L' (eg in OL)
|
|
* 'A', 'd', 'E', 'F', 'h', 'I', 'L', 'n', 'P', 'r', 'S', 't', or 'U' for Unknown
|
|
*
|
|
*/
|
|
unsigned char segChar(unsigned char DEFA, unsigned char xCGB)
|
|
{
|
|
unsigned char num, DEFA0CGB;
|
|
|
|
DEFA0CGB = (DEFA << 4) | (0x7 & xCGB);
|
|
switch (DEFA0CGB) {
|
|
case 0x0:
|
|
num = ' ';
|
|
break;
|
|
case 0xF5:
|
|
//TODO O or zero
|
|
num = '0';
|
|
break;
|
|
case 0x05:
|
|
//TODO I or 1
|
|
num = '1';
|
|
break;
|
|
case 0xD3:
|
|
num = '2';
|
|
break;
|
|
case 0x97:
|
|
num = '3';
|
|
break;
|
|
case 0x27:
|
|
num = '4';
|
|
break;
|
|
case 0xB6:
|
|
//TODO 5 or S
|
|
num = '5';
|
|
break;
|
|
case 0xF6:
|
|
num = '6';
|
|
break;
|
|
case 0x35:
|
|
num = '7';
|
|
break;
|
|
case 0xF7:
|
|
num = '8';
|
|
break;
|
|
case 0xB7:
|
|
num = '9';
|
|
break;
|
|
case 0x77:
|
|
num = 'A';
|
|
break;
|
|
case 0xC2:
|
|
num = 'c';
|
|
break;
|
|
case 0xD7:
|
|
num = 'd';
|
|
break;
|
|
case 0xF4:
|
|
num = 'E';
|
|
break;
|
|
case 0x72:
|
|
num = 'F';
|
|
break;
|
|
case 0x66:
|
|
num = 'h';
|
|
break;
|
|
case 0xE0:
|
|
num = 'L';
|
|
break;
|
|
case 0x46:
|
|
num = 'n';
|
|
break;
|
|
case 0x73:
|
|
num = 'P';
|
|
break;
|
|
case 0x42:
|
|
num = 'r';
|
|
break;
|
|
case 0xE2:
|
|
num = 't';
|
|
break;
|
|
default:
|
|
num = 'U';
|
|
break;
|
|
}
|
|
return num;
|
|
}
|
|
/*
|
|
* @brief Translates incoming bytes and generates the corresponding field of the status string
|
|
*
|
|
* @param byte, a character read from the protek multimeter
|
|
* @param byteNum, the position of the character in the protek protocol
|
|
* @param *str, translation of the byte into a "field:value" string where "value" is either an ASCII
|
|
* character or a boolean represented as a '1' or '0' character and "field" is a label.
|
|
* @return -1=ERROR, 0=NOTDONE, 1=DONE and str is set to translated bits
|
|
*
|
|
* The "switch" statement corresponds to the protocol table in the Protek 608 manual pg44
|
|
* with the labels for the bits written in reverse order so that they match
|
|
* the bit patterns we read from the multimeter
|
|
*/
|
|
int translateByte(struct protekData *data, char byte, int byteNum, char *str)
|
|
{
|
|
unsigned char num;
|
|
char field[64]="";
|
|
int gotSegChar=0;
|
|
|
|
switch (byteNum) {
|
|
case 1: /* SD3 p7,C,G,B */
|
|
data->xcgb = byte;
|
|
break;
|
|
case 2: /* SD3 D,E,F,A */
|
|
data->defa= byte;
|
|
gotSegChar=1;
|
|
strcpy(field, "SD3");
|
|
break;
|
|
case 3: /* SD4 p8,C,G,B */
|
|
data->xcgb = byte;
|
|
break;
|
|
case 4: /* SD4 D,E,F,A */
|
|
data->defa= byte;
|
|
gotSegChar=1;
|
|
strcpy(field, "SD4");
|
|
break;
|
|
case 5: /* SD5 p9,C,G,B */
|
|
data->xcgb = byte;
|
|
break;
|
|
case 6: /* SD5 D,E,F,A */
|
|
data->defa= byte;
|
|
gotSegChar=1;
|
|
strcpy(field, "SD5");
|
|
break;
|
|
case 7: /* bits 25-32 SUB UNIT DUTY2,HOLD2,RANGE2,ZD2 (ZENERDIODE) */
|
|
SETFIELD("DUTY2", "HOLD2", "RANGE2", "ZD2", byte);
|
|
break;
|
|
case 8: /* bits 25-32 SUB UNIT DC2,SIGN2,AC2,CONT2 */
|
|
SETFIELD("DC2", "SIGN2", "AC2", "CONT2", byte);
|
|
break;
|
|
case 9: /* BAR GRAPH PW1,x,B1,B0 */
|
|
SETFIELD("PW1", "X1", "B1", "B0", byte);
|
|
break;
|
|
case 10: /* BAR GRAPH B16,B8,B4,B2 */
|
|
SETFIELD("B16", "B8", "B4", "B2", byte);
|
|
break;
|
|
case 11: /* DC1,SIGN1,AC1,LOBAT */
|
|
SETFIELD("DC1", "SIGN1", "AC1", "LOBAT", byte);
|
|
break;
|
|
case 12: /* MD5 D,E,F,A */
|
|
data->defa = byte;
|
|
break;
|
|
case 13: /* MD5 p4,C,G,B */
|
|
data->xcgb = byte;
|
|
gotSegChar=1;
|
|
strcpy(field, "MD5");
|
|
break;
|
|
case 14: /* MD4 D,E,F,A */
|
|
data->defa = byte;
|
|
break;
|
|
case 15: /* MD4 p3,C,G,B */
|
|
data->xcgb = byte;
|
|
gotSegChar=1;
|
|
strcpy(field, "MD4");
|
|
break;
|
|
case 16: /* MD3 D,E,F,A */
|
|
data->defa = byte;
|
|
break;
|
|
case 17: /* MD3 p2,C,G,B */
|
|
data->xcgb = byte;
|
|
gotSegChar=1;
|
|
strcpy(field, "MD3");
|
|
break;
|
|
case 18: /* x,x,+,- */
|
|
SETFIELD("X2", "X3", "PLUS", "MINUS", byte);
|
|
break;
|
|
case 19: /* RS232C,AUTOOFF,x,PULSE */
|
|
SETFIELD("RS232C", "AUTOOFF", "X4", "PULSE", byte);
|
|
break;
|
|
case 20: /* STORE,AVG,MINUSPEAK,MIN */
|
|
SETFIELD("STORE", "AVG", "MINUSPEAK", "MIN", byte);
|
|
break;
|
|
case 21: /* RECALL,REL,PLUSPEAK,MAX */
|
|
SETFIELD("RECALL", "REL", "PLUSPEAK", "MAX", byte);
|
|
break;
|
|
case 22: /* GO/NG,REF,MINUSPER,PLUSPER */
|
|
SETFIELD("GO/NG", "REF", "MINUSPER", "PLUSPER", byte);
|
|
break;
|
|
case 23: /* MD2 D,E,F,A */
|
|
data->defa = byte;
|
|
break;
|
|
case 24: /* MD2 p1,C,G,B */
|
|
data->xcgb = byte;
|
|
gotSegChar=1;
|
|
strcpy(field, "MD2");
|
|
break;
|
|
case 25: /* MD1 D,E,F,A*/
|
|
data->defa = byte;
|
|
break;
|
|
case 26: /* MD1 x,C,G,B */
|
|
data->xcgb = byte;
|
|
gotSegChar=1;
|
|
strcpy(field, "MD1");
|
|
break;
|
|
case 27: /* s1,DEGF1,Hz1,x */
|
|
SETFIELD("s1", "DEGF1", "Hz1", "X5", byte);
|
|
break;
|
|
case 28: /* F1,A1,OHM1,x */
|
|
SETFIELD("F1", "A1", "OHM1", "X6", byte);
|
|
break;
|
|
case 29: /* DEGC1,S1,V1,k1 */
|
|
SETFIELD("DEGC1", "S1", "V1", "k1", byte);
|
|
break;
|
|
case 30: /* n1,m1,u1,M1 */
|
|
SETFIELD("n1", "m1", "u1", "M1", byte);
|
|
break;
|
|
case 31: /* BAR GRAPH B8K,B16K,x,x */
|
|
SETFIELD("B8K", "B16K", "X7", "X8", byte);
|
|
break;
|
|
case 32: /* BAR GRAPH B4K,B2K,B1K,B512 */
|
|
SETFIELD("B4K", "B2K", "B1K", "B512", byte);
|
|
break;
|
|
case 33: /* BAR GRAPH B32,B64,B128,B256 */
|
|
SETFIELD("B32", "B64", "B128", "B256", byte);
|
|
break;
|
|
case 34: /* BAR GRAPH M2,G2,m2,PERCENT2 */
|
|
SETFIELD("M2", "G2", "m2", "PERCENT2", byte);
|
|
break;
|
|
case 35: /* bits 133-143 SUB UNIT k2,OHM2,V2,dBm2 */
|
|
SETFIELD("k2", "OHM2", "V2", "dBm2", byte);
|
|
break;
|
|
case 36: /* bits 133-143 SUB UNIT x,Hz2,A2,DEGK2 */
|
|
SETFIELD("X9", "Hz2", "A2", "DEGK2", byte);
|
|
break;
|
|
case 37: /* SD1 x,C,G,B */
|
|
data->xcgb = byte;
|
|
break;
|
|
case 38: /* SD1 D,E,F,A */
|
|
data->defa= byte;
|
|
gotSegChar=1;
|
|
strcpy(field, "SD1");
|
|
break;
|
|
case 39: /* SD2 p6,C,G,B */
|
|
data->xcgb = byte;
|
|
break;
|
|
case 40: /* SD2 D,E,F,A */
|
|
data->defa= byte;
|
|
gotSegChar=1;
|
|
strcpy(field, "SD2");
|
|
break;
|
|
case 41:
|
|
/* Last byte is undocumented but it seems to be the rotary switch position
|
|
Positions range from 0 (V) to 8 (A).
|
|
*/
|
|
sprintf(field, "ROTSWITCH:%X", byte);
|
|
break;
|
|
default:
|
|
return -1;
|
|
break;
|
|
}
|
|
|
|
if (field[0] != '\0') {
|
|
if (gotSegChar) {
|
|
num = segChar(data->defa, data->xcgb);
|
|
if (num == 'U') {
|
|
return -1;
|
|
}
|
|
if ((010 & data->xcgb) == 010) {
|
|
sprintf(str, "%s:%c.", field, num);
|
|
} else {
|
|
sprintf(str, "%s:%c", field, num);
|
|
}
|
|
} else {
|
|
strcpy(str,field);
|
|
}
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* @brief Constructs a status string from the display map sent by the Protek
|
|
*
|
|
* The multimeter sends two data packets per second bounded by '['(0x5B) and ']'(0x5D)
|
|
* NOTE: Sometimes it sends 0x1D instead of 0x5D at the end of a packet
|
|
*/
|
|
int Protek608Reading(Ascon *a)
|
|
{
|
|
int ret, DS_AllocFailed = 0, transByteStatus = 0, transByteFailed = 0;
|
|
int errNum = 0;
|
|
char chr=0, field[64], errMsg[ERRLEN];
|
|
struct protekData *data = a->private;
|
|
|
|
/* a->readState == 0 means we haven't yet seen the start of a packet (ie the '[')
|
|
* a->readState > 0 is the current byte number
|
|
*/
|
|
while ( ( ret = AsconReadChar(a->fd, &chr) ) > 0) {
|
|
a->start = DoubleTime();
|
|
if (a->readState) {
|
|
if (a->readState >= LASTBYTENUM) {
|
|
a->readState = 0;
|
|
a->state = AsconReadDone;
|
|
data->first = 1;
|
|
if (0 == DynStringConcatChar(a->rdBuffer, '\0')) {
|
|
strcpy(errMsg, "PROTEK608: DynStringConcat or DynStringConcatChar failed:");
|
|
errNum = ENOMEM;
|
|
DS_AllocFailed = 1;
|
|
break;
|
|
}
|
|
if (chr != ']' && chr != 0x1D) {
|
|
snprintf(errMsg, ERRLEN, "PROTEK608: Unexpected value %X for last byte:", chr);
|
|
AsconError(a, errMsg, 0);
|
|
}
|
|
return 1;
|
|
}
|
|
transByteStatus = translateByte(a->private, chr, (a->readState)++, field);
|
|
if (transByteStatus == 1) {
|
|
if (data->first) {
|
|
if ( 0== DynStringConcat(a->rdBuffer, field) ) {
|
|
strcpy(errMsg, "PROTEK608: DynStringConcat or DynStringConcatChar failed:");
|
|
errNum = ENOMEM;
|
|
DS_AllocFailed = 1;
|
|
break;
|
|
}
|
|
data->first=0;
|
|
} else {
|
|
if ( 0 == DynStringConcatChar(a->rdBuffer, '|') ) {
|
|
strcpy(errMsg, "PROTEK608: DynStringConcat or DynStringConcatChar failed:");
|
|
errNum = ENOMEM;
|
|
DS_AllocFailed = 1;
|
|
break;
|
|
}
|
|
if ( 0 == DynStringConcat(a->rdBuffer, field) ) {
|
|
strcpy(errMsg, "PROTEK608: DynStringConcat or DynStringConcatChar failed:");
|
|
errNum = ENOMEM;
|
|
DS_AllocFailed = 1;
|
|
break;
|
|
}
|
|
}
|
|
} else if (transByteStatus == -1) {
|
|
snprintf(errMsg, ERRLEN, "PROTEK608: Failed to translate %X in byte number %d:", chr, a->readState-1);
|
|
errNum = 0;
|
|
transByteFailed = 1;
|
|
break;
|
|
}
|
|
} else if (chr == '[') {
|
|
a->readState = 1;
|
|
}
|
|
}
|
|
|
|
if (ret < 0) {
|
|
AsconError(a, "PROTEK608: AsconReadChar failed:", errno);
|
|
return 1;
|
|
}
|
|
if (DS_AllocFailed || transByteFailed) {
|
|
a->state = AsconReadDone;
|
|
a->readState = 0;
|
|
data->first = 1;
|
|
AsconError(a, errMsg, errNum);
|
|
return 1;
|
|
}
|
|
if (a->timeout > 0) {
|
|
if (DoubleTime() - a->start > a->timeout) {
|
|
AsconError(a, "PROTEK608: read timeout", 0);
|
|
a->state = AsconTimeout;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* @brief Protocol handler for Protek 608 multimeter
|
|
*
|
|
* It overrides the AsconWriteStart and AsconReading states of the standard handler.
|
|
* Available commands,
|
|
* "STATE": Requests a state report
|
|
*
|
|
* NOTE: The multimeter is always sending a display map at 0.5 second intervals, the "STATE" command
|
|
* simply forces a transition from the AsconWriteStart state to the AsconReadStart state.
|
|
*/
|
|
int Protek608ProtHandler(Ascon *a)
|
|
{
|
|
char *wrBuffArr, errMsg[ERRLEN];
|
|
switch(a->state) {
|
|
case AsconWriteStart:
|
|
wrBuffArr = GetCharArray(a->wrBuffer);
|
|
if (strncasecmp(wrBuffArr, "STATE", CMDLEN) != 0) {
|
|
a->state = AsconIdle;
|
|
snprintf(errMsg, ERRLEN, "PROTEK608: Unknown command %s", wrBuffArr);
|
|
AsconError(a, errMsg, 0);
|
|
return 0;
|
|
}
|
|
a->wrPos = 0;
|
|
AsconReadGarbage(a->fd);
|
|
a->state = AsconReadStart;
|
|
return 1;
|
|
break;
|
|
case AsconReading:
|
|
return Protek608Reading(a);
|
|
break;
|
|
default:
|
|
return AsconStdHandler(a);
|
|
}
|
|
}
|
|
|
|
int AsconProtek608Init(Ascon *a, SConnection *con, int argc, char *argv[])
|
|
{
|
|
/* greater than one when reading data */
|
|
a->readState = 0;
|
|
struct protekData *data;
|
|
a->private = data = (struct protekData *) malloc(sizeof(struct protekData));
|
|
a->killPrivate = protKill;
|
|
data->defa = 0;
|
|
data->xcgb = 0;
|
|
data->first = 1;
|
|
return AsconStdInit(a,con, argc, argv);
|
|
}
|
|
|
|
void AddProtek608Protocol()
|
|
{
|
|
AsconProtocol *prot = NULL;
|
|
|
|
prot = calloc(sizeof(AsconProtocol), 1);
|
|
prot->name = strdup("protek608");
|
|
prot->init = AsconProtek608Init;
|
|
prot->handler = Protek608ProtHandler;
|
|
AsconInsertProtocol(prot);
|
|
}
|