LabWindows/CVI

cancel
Showing results for 
Search instead for 
Did you mean: 

parsing ASCII as hex from a buffer

Solved!
Go to solution

 

Spoiler
<rant> Good grief, working with strings in C is just painful </rant>

I'm looking for an efficient way to extract ASCII-encoded bytes from a character buffer.  For instance, I have the following MODBUS data packet received from a slave instrument (hex format):

 

3A 30 31 30 33 30 32 30 32 42 45 33 41 0D 0A

This instrument and my driver are working in the ASCII domain, so this actually looks like:

 

:01030202BE3A\r\n

 

Now the trick here is that certain groups of bytes must be "lumped" together to be understood.  For instance, here is how I will need to parse these into the appropriate elements of the protocol:

 

element	byte width	data type
:	1		char
01 	2		char
03 	2		char
02 	2		char
02BE  	4		short 
3A 	2		char
\r	1		char
\n	1		char

Not really a big deal, but I'd like to avoid "hard-coding" some of these widths, particularly when dealing with variable output register data.

 

I had planned to use sscanf to parse these out of the string, but I discovered that you can't use variable width specifiers ( %*X ) like you can with sprintf.

 

For instance, here's what I do to build my ASCII MODBUS queries:

 

sprintf(query, 
"%c%s%0*X%s",
MODBUS_MSG_Header, // :
qryPayload, // string
MODBUS_MSG_LRC_LEN, // 2
Modbus_CalcLRC(qryPayload), // lLRC
MODBUS_MSG_Trailer); // CR LF

 

Then I looked into using the CVI formatio.h Scan function.  It has has something close:

 

s = "ffff";
Scan (s, "%s>%x[b2]", &a); /* result: a = 65535

But it doesn't appear that I can handle adjacent characters in an array with no whitespace.

 

 

Am I (probably) overlooking something simple here?

0 Kudos
Message 1 of 6
(2,811 Views)

@ElectroLund  ha scritto:

But it doesn't appear that I can handle adjacent characters in an array with no whitespace.

 


Well, this is really possible with a bit of tweaking on Scan format string Smiley Wink

 

The following is a complex command that scans a string received from an instrument. The string is a sequence of non-separated, 2-bytes measures that are interpreted as unsigned shorts (with byte ordering determined by 'o' modifier). The command is parametric, i.e. the first 2 parameters must be the (same) number of measures to extract from the string:

unsigned short	measures[nMeas];

Scan (string, "%*i[zb2o10]>%*i[b2]", nMeas, nMeas, measures);

 

Edit: this considers a sequence of fixed-width elements, but I suppose it can be modified to handle a  message with variable width elements.



Proud to use LW/CVI from 3.1 on.

My contributions to the Developer Community
________________________________________
If I have helped you, why not giving me a kudos?
Message 2 of 6
(2,782 Views)
Solution
Accepted by topic author ElectroLund

Rethinking to my previous post, it appears that that solution does not apply to your situation since it is targeted to binary data parsing.

 

The best I can think of to somewhat automate decoding the instrument response is something along this line, that leverages the use of asterisks in Scan command:

Scan (msg, "%c%s[w*]%s[w*]%s[w*]%s[w*]%s[w*]", &header, 2, d1, 2, d2, 2, d3, 4, payload, 2, lrc);

For each item you have to set both the item lenght and the variable where to store it (this can be simplified since d1, d2, d3 and possibly the LRC in Modbus protocol should be always 2-bytes long). This command, however, simply extracts the appropriate substrings that you need to post process individually: it seems not so different to me than using something like this (which again can be parametrized setting the width of payload in the scan string):

Scan (msg,     "%c", &header);             x =  NumFmtdBytes ();
Scan (msg + x, "%s[w2]>%x[b2]", &c[0]);    x += NumFmtdBytes ();
Scan (msg + x, "%s[w2]>%x[b2]", &c[1]);    x += NumFmtdBytes ();
Scan (msg + x, "%s[w2]>%x[b2]", &c[2]);    x += NumFmtdBytes ();
Scan (msg + x, "%s[w4]>%x",     &payload); x += NumFmtdBytes ();
Scan (msg + x, "%s[w2]>%x[b2]", &lrc);     x += NumFmtdBytes ();

 



Proud to use LW/CVI from 3.1 on.

My contributions to the Developer Community
________________________________________
If I have helped you, why not giving me a kudos?
Message 3 of 6
(2,774 Views)

Thanks, Roberto!  I didn't realize that the [w] specifier could be used with an asterisk.  So here's what I did...

 

#define MODBUS_MSG_MAX_LENGTH 256
#define MODBUS_MSG_START_LEN 1
#define MODBUS_MSG_LRC_LEN 2

char rspStart = 0;
char resPayload[MODBUS_MSG_MAX_LENGTH];
int rspChecksum = 0; // for some reason, this MUST be an int
int rspLength = 0; // rspLength = ComRdTerm();

Scan(response, "%s>%c%s[w*]%x[w*]", &rspStart, rspLength - (MODBUS_MSG_START_LEN + MODBUS_MSG_LRC_LEN), // the payload is variable in length rspPayload, MODBUS_MSG_LRC_LEN, &rspChecksum);

 

0 Kudos
Message 4 of 6
(2,677 Views)

Meanwhile....

 

This apparently isn't legal, as I get the error "The number of arguments exceeds the maximum supported"

 

Scan(response, 
     "%s>%i[b*]%i[b*]%i[b*]%s[w*]", 
     MODBUS_MSG_ADDR_LEN,
     &rspSlave,
     MODBUS_MSG_FUNC_LEN,
     &rspCode,
     nRegs * 2,
     &rspBytes,
     rspData);

So apparently, I'm going to have to do this piecemeal like your example.

0 Kudos
Message 5 of 6
(2,665 Views)

Found my issue.  

It's illegal to use the asterisk with the b modifier (as far as I can tell).

 

A better approach is a modified version of your earlier proposal, specifying instead the width of the source string, rather than the length of the target integers, which is such a fine subtlety it took me a while to grasp it:

 

Scan(response + i, "%s[w*]>%x[b1]", MODBUS_MSG_ADDR_LEN, 	 &newrspSlave);	i += NumFmtdBytes();
Scan(response + i, "%s[w*]>%x[b1]", MODBUS_MSG_FUNC_LEN, 	 &newrspCode);	i += NumFmtdBytes();
Scan(response + i, "%s[w*]>%x[b1]", nRegs * 2, 			 &newrspBytes);	i += NumFmtdBytes();
Scan(response + i, "%s[w*]>%s",     nRegs * MODBUS_MSG_DATA_LEN, rspData));

This works nicely.  

 

0 Kudos
Message 6 of 6
(2,663 Views)