Home AMX User Forum NetLinx Studio

Hex Codes in DATA.TEXT

I am writing a module to control a TagMcLaren DVD32R & AV192R via the 'TagTronic Bus' - this is a rs485 protocol built into all tag products.

I have no experience dealing with raw hex data and am struggling to work out how to best extract the data from the DATA.TEXT property.

When I switch on diagnostics for the rs485 port I see the following...

Line 31 :: String From [5001:4:1]-[$02$00$81$00$C1$80$02$01$FEx$89$E4$03] - 08:51:18

Message definition is...

Byte 1: STX
Byte 2&3: Sender Address
Byte 4&5: Target Address
Byte 6&7: Message Header
Byte 8&9: CRC
Byte 10: ETX

I have attempted to parse the string into a series of long variables i.e....

STACK_VAR LONG lSTX, lSender, lTarget, lHeader, lCRC,lETX

lSTX = GET_BUFFER_CHAR(DATA.TEXT)
lSender = GET_BUFFER_STRING(DATA.TEXT,2)

etc etc

Problem is that i receive the following error message in the diagnotics log...

(0011093491) GetString - Error 1 Tk=0x0001


I have read some of the amx tech docs & other postings in this forum and can't quite decide what i'm doing wrong or how to resolve it.

Should I be using longs to take in this data or another data type? I have also tried using the ATOL funtion to assign the variables but again I receive errors in the diagnostics log.

From what I understand the $ notation for hex is an AMX thing which means the string I see from the device is already going through some type of translation in the netlinx master. trouble is I cannot work out what its doing...

An issue i'm sure I will have once this is resolved is that the sender & target addresses need to further break down into 3 values, 1st 4 bits are unused, 2nd 6 bits is the group address & 3rd 6 bits is the device address. I am presuming I handle this by doing an 'X = Y << 4' ??

Swimming well out my depth here...help!!!

Regs, Phil

Comments

  • Hex Codes in DATA.TEXT

    First, you are on the right track and the fact that you are getting data from the device is half the battle!

    Second, Netlinx is showing you the data as $02, $81, etc. since these represent non-printable Ascii characters.

    Third, don't get hung up on HEX representation and how to store it in Netlinx. You have a data buffer which is essentially an array of CHARs (array of bytes). STX is $02 in hex but it is still stored in a single byte. The 0 represents the first 4-bits of the byte (0000) and 2 represents the last 4-bits in the byte (0010). Keep in mind that a byte can range in values from $00 to $FF so you can parse your HEX device data as a series of bytes or CHARs. Your device uses a HEX protocol which is byte oriented but many of the bytes are not printable Ascii characters. Many devices use Ascii oriented protocols and they are easier to read and interpret in log files and debug streams but at the end of the day, both techniques are byte oriented and you only need to interpret the data (parse and compare) accordingly.

    So, to answer your question, move the HEX data returned from the device in DATA.TEXT to a CHAR array that is suitable in size to hold the data and then parse it accordingly. You can check for specific values by doing comparisons between values in the buffer and a specific HEX value, for example:
    IF(cBuffer[1] == $02)
    {
       this is an STX, do something ...
    }
    
    You may even want to code your DATA_EVENT handler [STRING subevent] to look for the ETX in the data stream before you move the data from the buffer to begin parsing. You could do something like:
    IF(FIND_STRING(DATA.TEXT, "$03", 1))
    {
       cBuffer = REMOVE_STRING(DATA.TEXT, "$03", 1)
    
       parse the data in cBuffer ...
    }
    
    You can also get more sophisticated by using COMPARE_STRING to examine the buffer for a sequence of HEX values using the Netlinx concatenation operator:
    IF(COMPARE_STRING(cBuffer, "$02,$81,$82,$03"))
       do something ....
    
    As for breaking apart HEX values (examining bit patterns within a byte), using shift and masking operations as you noted is a way to do it. It is not the only way depending on the range of values you might get back from a device but it is certainly one of the better ways to accomplish what you are trying to do.

    Hope this helps --

    Reese
  • DHawthorneDHawthorne Posts: 4,584
    Your GET_BUFFER functions do not return LONGs. _CHAR returns a CHAR, and _STRING returns a CHAR array, and your target variables are all longs. That is why you are getting the errors. Furthermore, they are only useful if you know in advance how big your packet is going to be. Also, you are taking a two character aray in your _STRING function and trying to assign it to a single numerical value.

    First of all, DATA.TEXT is transient. Chances are you can fully process whatever comes in before a new DATA_EVENT is generated, but once that happens, it's gone and replaced with the new stuff. What you should do is create a large CHAR array and use CREATE_BUFFER in DEFINE_START to link your port to that array. You can then forget about DATA.TEXT - all your data will be in the array. You can run a test like Reese described to look for your ETX character in the array to determine if it needs processing. You can put that test in mainline, or trigger it on the STRING handler of your EVENT.

    I would personally create a smaller CHAR array to hold each packet at this point, and use something like sPacket = REMOVE_STING(sBigBuffer, "$03", 1) to populate it. You can then break out your elements into your target variables:

    sTemp = MID_STRING(sPacket, 2, 2) ;
    ISENDER = (ATOL("sTemp[1]") * 16) + ATOL("sTemp[2]") ;

    The ATOL function takes care of converting to your target LONG. Depending on the form that address takes in HEX, you might not be able to do a direct extraction like in my example - it assumes a two-digit hex value, and not some ASCII representation. You may have to play around with the conversions. Like Reese said, don't get too hung up on the fact it's hex - a single character is a single hex didgit, and a string is just an array of them. So all your string functions can be used to manipulate them.

    Notice every time I use a string function on a single character I enclose it in double quotes. This is necessary. Sometimes you will get a compiler error if you don't do this, but a lot of the time, it just plain misinterprets the data. Think of it as a type cast - those functions expect a string, and if your single character needs to be treated as a string of length one, so be it.
  • hodeyphodeyp Posts: 104
    Cheers, i'll have a play with the code and see if I can get this thing working!!
  • hodeyphodeyp Posts: 104
    thanks for your advice, the array is now being populated from the buffer and all values look to be correct.

    However, the protocol has a 2 byte value that needs to be parsed to return 3 values. The first 4 bits are reserved for future functionality, the other 2 groups of 6 bits are for group & device addresses. This gives a possible 63 groups & 63 devices and I need to know what these are to send messages to the correct device. I have tried using the LSHIFT function to remove the bits sequentially and convert to an integer value but have hit a stumbling block as the LSHIFT function only works on integers. Is it possible to combine the 2 hex char values into a single integer to do the bitwise stuff?

    You'll have to excuse my terminology if its incorrect, I am very much a novice at low level byte & bit manipulation!!

    An example of the address is "$01,$81" which by my calculations equate to a group address of 6 and a device address of 1 (bits 5-10 = 000110 & bits 11-16 = 000001)

    Any help here would be very much appreciated!!
  • jeffacojeffaco Posts: 121
    Example of bitwise manipulation in NetLinx

    My TimeSync (the basis for i!-TimeManager) project does significant bitwise manipulation due to the SNTP protocol. I'd suggest you take a look at that and, if you have further questions, post them.

    Here's a link where you can download the sources:

    http://cvs.sourceforge.net/viewcvs.py/netlinx-modules/NetLinx-Modules/TimeSync/

    Hope this helps,

    -- Jeff
  • hodeyphodeyp Posts: 104
    magic - cheers!!
  • DHawthorneDHawthorne Posts: 4,584
    The example code I gave before would have converted your 2 hex digits to a single LONG, assuming it's a big-endian notation all the way through, and I hadn't messed it up :). It doesn't need to be a LONG (I was just following what you originally posted), using ATOI instead of ATOL will put in in INTEGER form. For these numbers, it comes out to the same value, just a different variable type. But I screwed it up by multiplying by 16 - went to fast translating in my head to decimal and forgot we were dealing with a byte and not a nibble. It would have been better to keep the whole thing in hex. In decimal, it should have been 256 to make it fill out the byte value properly. Here's the correct transform:

    sTemp = MID_STRING(sPacket, 2, 2) ;
    ISENDER = (ATOI("sTemp[1]") * $100) + ATOI("sTemp[2]") ;

    If sTemp = "$1,$81", then ISENDER will be $185, or 110000001 in binary. You can do your bitwise operations from there.
  • frthomasfrthomas Posts: 176
    hodeyp wrote:
    An issue i'm sure I will have once this is resolved is that the sender & target addresses need to further break down into 3 values, 1st 4 bits are unused, 2nd 6 bits is the group address & 3rd 6 bits is the device address. I am presuming I handle this by doing an 'X = Y << 4' ??

    Now that you are closer to that problem...

    To have an integer that carries the group address, you want the 6 bits to be the last 6bits of an 8 bits integer...
    UUUUGGGG GGDDDDDD
    ---B1--- ---B2---
    
    =>
    
    00GGGGGG
    -group--
    
    So you want to make sure the 4 unused bits are 0. You can do that by "and-ing" 0x0F to the first byte above.
    group = $0F & B1;
    
    0000GGGG
    -group--
    
    Then shift group by two places
    group = group << 2;
    
    00GGGG00
    -group--
    
    Then add to that the 2 remaining bits, shifted so that they are in the right place...
    temp = B2 >> 6;
    
    000000GG
    --temp--
    
    group = group + B2;
    
      00GGGG00
    + 000000GG
      ========
      00GGGGGG
    
    Voila. You can do it all in one shot, so that:
    group = (($0F & B1) << 2) + (B2 >> 6);
    
    For the device address it is simpler, you just need to mask the first 2 bits of B2...
    deviceaddress = B2 & $3F;
    
    HTH

    Fred
  • hodeyphodeyp Posts: 104
    All, many thanks for your contributions - all have been very useful in helping me understand how to manipulate hex. I have now sucessfully decoded the various hex codes in the correct manner.

    My last question (I promise!) - there are 2 types of message the bus generates, one is a fixed length 10byte message - all is working ok here now. The other message is variable length with bytes 8&9 detailing byte length for the variable length message (bytes 10 to 'n').

    Byte 9 is actually the logical inverse of byte 8 as a form of error checking. I have this working ok and am receiving 100% success rate on checking.

    Now for the problem...

    The length field is again hex, the contoller is not extracting the correct number and instead returning an error about invalid conversion.

    e.g. for a data length of 2 bytes the hex being returned is $02. From what I can see the code is trying to convert this into the ascii equivalent to determine the number so is in fact returning the ASCII 'STX' value. I have played about with the various HEXTOI, ATOI and similar functions but all seem to return the same result, the ascii representation of $02.

    I'm not sure if i'm being a bit thick here but I can't seem to find a way of making it return the correct data :(
  • DHawthorneDHawthorne Posts: 4,584
    $02 = 2 (decimal) = STX (ASCII)

    It's the same number no matter how you look at it, only in binary would it look different (10). It sounds to me like it is doing the right thing; trying to display the ASCII character is just a display issue, not a value issue. What line is generating the error? Heh, and what are the lines adjacent? The Studio compiler often highlights the wrong line for an error.
  • hodeyphodeyp Posts: 104
    Code is:

    Length = GET_BUFFER_STRING(TagTronicBuffer,2)
    IF(Length[1] BXOR Length[2] = $FF)
    {
    DataBytes = GET_BUFFER_STRING(TagTronicBuffer,Length[1])
    SEND_STRING 0,"'DataBytes:',DataBytes"
    }

    the code compiles ok but i get an error when I enable diagnostics and the SEND_STRING returns [DataBytes:]. If it is converting is it not looking for hex $32?
  • frthomasfrthomas Posts: 176
    Well, SEND_STRING expects a string as an argument. A string is an array of CHAR. Your Databyte variable is whatever type but contains $02, and you want to SEND_STRING the STRING "2", that is the array of char that corresponds to the representation of the number 2 (or $02). The function to use is ITOI (Integer TO Array). Take the integer in the variable (2) and convert that to a string ("2").

    SEND_STRING 0,"'DataBytes:', ITOA(DataBytes)"

    should give you "DataBytes:2"...

    HTH

    Fred
  • DHawthorneDHawthorne Posts: 4,584
    Here's a little snippet I use just for diagnostic messages. Use it like this in your code: SEND_STRING 0, sCleansAscii("'DataBytes:',DataBytes"). It will display printable ASCII characters normally, and low and high order ASCII as the raw value in <> brackets. So in your code, it would show "DataBytes:<2>" if your value for DataBytes was 2. You could also use the ITOA function as well; but this has more general applications (like showing <13> for a CR character instead of filling your display with blank lines). Just another option for you...Fred has the right of it, you are getting a blank there because you are sending a low-order character directly to the temrinal, and it has no way to display it. Heh, try doing it with $7 some day to annoy whoever works next to you.
    DEFINE_FUNCTION CHAR[5096] sCleanAscii (CHAR sString[])
    {
        STACK_VAR CHAR sTemp[5096] ;
        STACK_VAR INTEGER nCount ;
        
        FOR(nCount = 1; nCount <= LENGTH_STRING(sString); nCount ++)
        {
    	SELECT
    	{
    	    ACTIVE(sString[nCount] < 32) : // low order ASCII
    	    {
    		sTemp = "sTemp, ',<', ITOA(sString[nCount]), '>'"
    	    }	
    	    ACTIVE(sString[nCount] > 126) : // high order ASCII
    	    {
    		sTemp = "sTemp, ',<', ITOA(sString[nCount]), '>'"
    	    }	
    	    ACTIVE(TRUE) : sTemp = "sTemp, sString[nCount]" ;
    	}
        }
        
        RETURN sTemp ;
    }
    
  • hodeyphodeyp Posts: 104
    cheers everybody for all your help - module now complete and functioning perfectly!
Sign In or Register to comment.