#include #include #include "ascon.h" #include "ascon.i" #include "dynstring.h" #include "cnvrt.h" /* * this is a (scriptcontext) general binary protocol driver * * Markus Zolliker Aug 2010 * * conversion to and from binary * * Syntax: * space separated items, the command first, a slash and then the read format * * commandItem ... / formatItem ... * * where a commandItem one of the following * * a number converted according to the choosen format, i1 is default * int changing the format to byte integers * hex changing the format to hexadecimal (1 byte at a time) * float changing the format to 4 byte ieee float * crc send crc * * and formatItem is one of the follwing * * skip skip one byte * skip skip bytes * code returned function code, when bit7 is set, the response is * recogized as an error message and the response is dumped * the result * int convert bytes to integer (most significant byte first) * and append the (decimal coded) number to the result * if is omitted, = 1 is assumed * hex convert 1 byte to hexadecimal coded integer * and append it to the response * float convert 4 bytes from ieee float * and append the number to the response * crc check crc (if wrong, "badCRC" is added to the response) * * multiple items in the response are space separated * * Usage example: Modbus read (float) * command: address 250, function 3, start-address 8, size 2, crc * response: address, function, byte-count, float, crc * * sct send 250 3 int2 10 2 crc / skip code skip float crc * * different crc's might be used (argv[2] opf BinInit): * modbus-crc: CRC-16-IBM, order: lo,hi * keller-crc: CRC-16-IBM, order: hi,lo * sycon-crc: sort of mod 256 checksum, byte stuffing included (no float allowed) */ typedef enum {intType, hexType, floatType, skipType, codeType, crcType, dumpType, errorType} BinDataType; // dumpType, errorType must be the last items typedef enum {modbusCrc, kellerCrc, rsportCrc, syconCrc} CrcAlgorithm; typedef struct { CrcAlgorithm crcAlgorithm; pDynString inp; char *nextFmt; pDynString result; int expectedChars; BinDataType type; long iValue; int dumpFrom; int syconState; unsigned char chksum; } BinPrivate; /*----------------------------------------------------------------------------*/ static int calc_crc(char *inp, int inpLen) { /** CRC-16-IBM Algorithm * crc calculation: * returns the 16bit CRC value of a message * crc check: * returns 0 when the crc appended to the message (lo byte first) * is correct */ unsigned int crc = 0xffff; unsigned int next; int carry; int n; while (inpLen--) { next = *(unsigned char *) inp; crc ^= next; for (n = 0; n < 8; n++) { carry = crc & 1; crc >>= 1; if (carry) { crc ^= 0xA001; } } inp++; } return crc; } /*----------------------------------------------------------------------------*/ static int calc_crc8(char *inp, int inpLen) { /** CRC-8 (x8+x5+x4+1) Algorithm * crc calculation: * returns the 8bit CRC value of a message * crc check: * returns 0 when the crc appended to the message is correct */ unsigned char crc = 0; unsigned char data; int n; while (inpLen--) { data = *(unsigned char *) inp; for (n = 0; n < 8; n++) { if ((crc ^ data) & 1) { crc ^= 0x18; crc >>= 1; crc |= 0x80; } else { crc >>= 1; crc &= 0x7f; } data >>= 1; } inp++; } return crc; } /*----------------------------------------------------------------------------*/ int BinReadItem(Ascon *a) { BinPrivate *p = a->private; char item[32]; int valen; int size; if (sscanf(p->nextFmt, "%30s%n", item, &valen) <= 0) { if (p->type < dumpType) { p->dumpFrom = GetDynStringLength(a->rdBuffer); p->type= dumpType; } p->expectedChars = 999; return 0; } if (strcasecmp(item, "crc") == 0) { p->type = crcType; p->expectedChars = 2; p->nextFmt += valen; return 1; } else if (strncasecmp(item, "int", 3) == 0) { size = 1; sscanf(item + 3, "%d", &size); p->expectedChars = size; p->type = intType; p->iValue = 0; } else if (strcasecmp(item, "float") == 0) { p->expectedChars = 4; p->type = floatType; } else if (strcasecmp(item, "hex") == 0) { p->expectedChars = 1; p->type = hexType; } else if (strcasecmp(item, "code") == 0) { p->type = codeType; } else if (strncasecmp(item, "skip", 4) == 0) { size = 1; sscanf(item + 4, "%d", &size); p->expectedChars = size; p->type = skipType; } else { return -1; } p->nextFmt += valen; return 1; } /*----------------------------------------------------------------------------*/ void BinError(Ascon *a, char *text) { BinPrivate *p = a->private; p->type= errorType; p->dumpFrom = 0; p->expectedChars = 1; DynStringCopy(a->errmsg, "BINERR: "); DynStringConcat(a->errmsg, text); a->state = AsconFailed; } /*----------------------------------------------------------------------------*/ void BinToSycon(pDynString dyn) { char *str = strdup(GetCharArray(dyn)); int l = GetDynStringLength(dyn); unsigned char sum, byte; int i; DynStringClear(dyn); DynStringConcat(dyn, "\x02\x10\x80"); /* STX ADDR CMD */ sum = 0x10 + 0x80; for (i=0; iprivate; switch (a->state) { case AsconWriteStart: /* exchange buffers */ dyn = p->inp; p->inp = a->wrBuffer; a->wrBuffer = dyn; DynStringClear(dyn); str = GetCharArray(p->inp); len = GetDynStringLength(p->inp); l = 0; size = 1; type = intType; for (pos = 0; pos < len; ) { if (sscanf(str + pos, "%30s%n", item, &valen) <= 0) { BinError(a, "missing '/'"); return 1; } pos += valen; if (strcasecmp(item, "/") == 0) { break; } else if (strcasecmp(item, "crc") == 0) { switch (p->crcAlgorithm) { case kellerCrc: crc = calc_crc(GetCharArray(dyn), l); DynStringConcatChar(dyn, crc / 256); DynStringConcatChar(dyn, crc % 256); break; case modbusCrc: crc = calc_crc(GetCharArray(dyn), l); DynStringConcatChar(dyn, crc % 256); DynStringConcatChar(dyn, crc / 256); break; case rsportCrc: crc = calc_crc8(GetCharArray(dyn), l); DynStringConcatChar(dyn, crc); break; } } else if (strncasecmp(item, "int", 3) == 0) { sscanf(item + 3, "%d", &size); type = intType; } else if (strcasecmp(item, "hex") == 0) { type = hexType; } else if (strcasecmp(item, "float") == 0) { type = floatType; } else { switch (type) { case intType: res = sscanf(item, "%ld", &iValue); if (res != 1) { BinError(a, "invalid integer"); return 1; } for (i = size - 1; i >= 0; i--) { if (i < sizeof data) { data[i] = iValue % 256; } iValue /= 256; } for (i = 0; i < size; i++) { if (i >= sizeof data) { DynStringConcatChar(dyn, 0); } else { DynStringConcatChar(dyn, data[i]); } } l += size; break; case hexType: res = sscanf(item, "%lx", &iValue); if (res != 1) { BinError(a, "invalid hex. integer"); return 1; } DynStringConcatChar(dyn, (iValue & 255)); break; case floatType: res = sscanf(item, "%lf", &fValue); if (res != 1) { BinError(a, "invalid float"); return 1; } double2ieee(fValue, data); DynStringConcatChar(dyn, data[0]); DynStringConcatChar(dyn, data[1]); DynStringConcatChar(dyn, data[2]); DynStringConcatChar(dyn, data[3]); l += 4; break; } } } if (p->crcAlgorithm == syconCrc) { BinToSycon(dyn); } p->nextFmt = str + pos; p->type = hexType; /* initialize to anything < dumpType */ do { res = BinReadItem(a); if (res < 0) { BinError(a, "illegal return format: "); DynStringConcat(a->errmsg, p->nextFmt); return 1; } } while (res == 1); p->nextFmt = str + pos; a->wrPos = 0; a->state = AsconWriting; a->lineCount = 0; return 1; case AsconReadStart: DynStringClear(p->result); p->type = hexType; /* initialize to anything < dumpType */ BinReadItem(a); p->syconState = 0; break; case AsconReading: res = AsconBaseHandler(a); if (res == 0) { if (GetDynStringLength(a->rdBuffer) == 0) { /* wait for the first byte - or timeout */ return res; } if (p->type >= dumpType) { l = GetDynStringLength(a->rdBuffer); str = GetCharArray(a->rdBuffer); for (i = p->dumpFrom; i < l; i++) { snprintf(item, sizeof item, "%2.2x ", (str[i] & 255)); DynStringConcat(p->result, item); } if (p->type == errorType) { DynStringConcat(a->errmsg, GetCharArray(p->result)); a->state = AsconFailed; } else { a->state = AsconReadDone; } /* exchange buffers */ dyn = a->rdBuffer; a->rdBuffer = p->result; p->result = dyn; return 1; } return res; } if (p->crcAlgorithm == syconCrc) { switch (p->syconState) { case 0: /* wait for STX */ if (a->lastChar == 0x02) { p->syconState = 1; p->chksum = 0; } return res; case 1: /* skip address */ case 2: /* skip cmd_rsp */ p->syconState++; p->chksum += a->lastChar; return res; case 3: /* detect stuffed bytes */ if (a->lastChar == 0x07) { p->syconState = 4; return res; } p->chksum += a->lastChar; break; case 4: /* last byte was 0x07 */ switch (a->lastChar) { case '0': a->lastChar = 0x02; break; case '1': a->lastChar = 0x0d; break; case '2': a->lastChar = 0x07; break; } p->chksum += a->lastChar; p->syconState = 3; break; case 5: /* expect 0x0d */ if (a->lastChar != 0x0d) { DynStringConcat(p->result, "noCR "); } p->syconState = 6; p->dumpFrom++; /* skip for dump */ case 6: /* skip everything else */ return res; } } /* convert according to type */ switch (p->type) { case codeType: if ((a->lastChar & 255) < 128) { p->expectedChars = 0; } else { DynStringCopy(a->errmsg, "BINERR: "); p->expectedChars = 2; p->type = errorType; p->dumpFrom = 0; /* skip to end */ p->nextFmt = ""; } break; case errorType: if (p->expectedChars > 1) { p->expectedChars--; snprintf(item, sizeof item, "error %d / ", (a->lastChar & 255)); DynStringCopy(p->result, item); } break; case dumpType: break; case skipType: p->expectedChars--; break; case intType: p->expectedChars--; p->iValue = p->iValue * 256 + (a->lastChar & 255); if (p->expectedChars <= 0) { snprintf(item, sizeof item, "%ld ", p->iValue); DynStringConcat(p->result, item); } break; case hexType: p->expectedChars--; if (p->expectedChars <= 0) { snprintf(item, sizeof item, "%2.2x ", (a->lastChar & 255)); DynStringConcat(p->result, item); } break; case floatType: /* does not work with sycon when stuffed */ p->expectedChars--; if (p->expectedChars <= 0) { str = GetCharArray(a->rdBuffer) + GetDynStringLength(a->rdBuffer) - 4; fValue = ieee2double(str); snprintf(item, sizeof item, "%.7g ", fValue); DynStringConcat(p->result, item); } break; case crcType: p->expectedChars--; if (p->expectedChars <= 0) { str = GetCharArray(a->rdBuffer); l = GetDynStringLength(a->rdBuffer); switch (p->crcAlgorithm) { case syconCrc: /* ignore crc check */ /* subtract CRC char (undo the addition) and subtract crc higher four bits */ p->chksum -= a->lastChar + (a->lastChar & 0x0f); p->syconState = 5; if (p->chksum != 0) { DynStringConcat(p->result, "badCRC "); } break; case kellerCrc: i = str[l-2]; str[l-2] = str[l-1]; str[l-1] = i; /* fall through */ case modbusCrc: case rsportCrc: if (calc_crc(str, l) != 0) { DynStringConcat(p->result, "badCRC "); } } } else if (p->crcAlgorithm == syconCrc) { /* subtract CRC char (undo the addition) and subtract crc higher four bits */ p->chksum -= a->lastChar + (a->lastChar & 0x0f) * 16; } } if (p->expectedChars <= 0) { BinReadItem(a); } return res; } return AsconBaseHandler(a); } static void BinPrivateKill(void *pVoid) { BinPrivate *p = pVoid; DeleteDynString(p->inp); DeleteDynString(p->result); free(p); } static int BinInit(Ascon * a, SConnection * con, int argc, char *argv[]) { BinPrivate *p; /* argv[2] may be modbus-crc (default), keller-crc, rsport-crc or sycon-crc */ if (argc < 2) { return 0; } p = calloc(sizeof(*p), 1); a->private = p; a->killPrivate = BinPrivateKill; p->crcAlgorithm = modbusCrc; if (argc > 2 && strcmp(argv[2], "") != 0) { if (strcasecmp(argv[2], "keller-crc") == 0) { p->crcAlgorithm = kellerCrc; } else if (strcasecmp(argv[2], "sycon-crc") == 0) { p->crcAlgorithm = syconCrc; } else if (strcasecmp(argv[2], "rsport-crc") == 0) { p->crcAlgorithm = rsportCrc; } else if (strcasecmp(argv[2], "modbus-crc") != 0) { SCPrintf(con, eError, "ERROR: unknown crc-algorithm %s", argv[2]); a->private = NULL; free(p); return 0; } } a->hostport = strdup(argv[1]); p->inp = CreateDynString(60,63); p->result = CreateDynString(60,63); if (argc > 3) { a->timeout = atof(argv[3]); } else { a->timeout = 2.0; /* sec */ } return 1; } /*----------------------------------------------------------------------------*/ void AddBinProtocol() { static AsconProtocol binprot; binprot.name = "bin"; binprot.handler = BinHandler; binprot.init = BinInit; AsconInsertProtocol(&binprot); }