Files
sics/site_ansto/hardsup/sct_protek608.c
2014-05-16 17:23:44 +10:00

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 0;
}
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 0;
}
if (DS_AllocFailed || transByteFailed) {
a->state = AsconReadDone;
a->readState = 0;
data->first = 1;
AsconError(a, errMsg, errNum);
return 0;
}
if (a->timeout > 0) {
if (DoubleTime() - a->start > a->timeout) {
AsconError(a, "PROTEK608: read timeout", 0);
/* a->state = AsconTimeout; */
}
}
return 0;
}
/*
* @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);
}