Red Lion DA30D, parsing binary string to (u)INT16/32/48, Float32, Bit16

antwes

Member
Join Date
Feb 2024
Location
Lidkoping
Posts
17
Hi folks,

I'm trying to parse a binary string on a Red Lion DA30D using a Raw UDP/IP input port. I've done this before with ASCII strings so I do have some experience on the Red Lion, and I've got experience on parsing similar binary strings in Python.

But parsing a binary string in Red Lion gives me some headache. The data types to be parsed out is of different byte sizes and data types, please see attached message definition.

In addition to this, the payload has a wrapper around it, and I've been able to extract the message payload by identifying the start characters, see code. From there, i do not know how to parse bytes into (u)INT16/32/48, Float32, Bit16 data types - Can someone please help, any ideas are welcome and minor clues might help as well!

Message Wrapper
LNAV-WRAPPER.png

Message Payload
LNAV.png

My start looks like this:
C#:
if (PortRead(7, 50) == 16)  // HEX 10
    {
    if (PortRead(7, 50) == 2)  // HEX 02
        {
        if (PortRead(7, 50) == 0)  // HEX 00
            {
            if (PortRead(7, 50) == 224)  // HEX E0
                {
                    // MESSAGE IS 'LNAV' TYPE
                    // PAYLOAD FOLLOWS HERE
                }
            else if (PortRead(7, 50) == 232) // HEX E8
                {
                    // MESSAGE IS 'LNAVUTC' TYPE
                    // PAYLOAD FOLLOWS HERE
                }
            }
        }
    }
 
Redlion have help notes and an example help file that might help:
The bit you are interested in is close to the end of the first document, RAW UDP Receiving. The << that you see moves the byte received in to the correct location of the tag. Don't start figuring I am an expert here :) this is just stuff that I read and stored away in deep dark memory for possible future use. In the example RxIP is a 32 bit Integer from 4 bytes, RxPort is a 16 bit integer from 2 bytes. So, a total guess here, you would declare a tag to be a Float, and then use the << to move the bytes in to the correct positions within the 32 bits to get your Float32 value. You might have to use the Manipulation drop down if the bytes aren't in the order expected by Crimson.
 
Hi @BryanG, and thanks for your advise, that really helped me a lot! I'm now able to decode most of the message, but I still have problem with some things:

- Time is unsigned integer, Red Lion seem to only use signed integers (Time is not very important here so not critical)
- I cant get Float32 values converted correct, it seem like they are sent out as Integer numbers even though the tags are Floats
- I do not know how to convert the last field from Integer to Bits, any ideas here?

Thanks
 
My current code:
C#:
int port;
int tmp;
port = 6;

// Check for Frame
if(PortRead(port, 0) == 'R')
    {
    // Read Source IP
    RxIP1 = PortRead(port, 0) << 0;
    RxIP2 = PortRead(port, 0) << 0;
    RxIP3 = PortRead(port, 0) << 0;
    RxIP4 = PortRead(port, 0) << 0;
    
    // Read Source Port
    RxPort = PortRead(port, 0) << 8;
    RxPort |= PortRead(port, 0) << 0;
    
    // Read Length
    RxCount = PortRead(port, 0) << 8;
    RxCount |= PortRead(port, 0) << 0;

    // Read Data
    int n;
    
    // Search for start character within payload
    for (n = 0; n < RxCount; n++)
        {
        if (PortRead(port, 0) == 16)  // HEX 10
            {
            if (PortRead(port, 0) == 2)  // HEX 02
                {
                if (PortRead(port, 50) == 0)  // HEX 00
                    {
                    tmp = PortRead(port, 0);
                    
                    if (tmp == 224 || tmp == 232)
                        {
                        if (tmp == 224)  // HEX E0
                            {
                            LNAV.HEADER = "LNAV " + IntToText(RxSeq, 10, 4);
                            }
                        else if (tmp == 232) // HEX E8
                            {
                            LNAV.HEADER = "LNAVUTC " + IntToText(RxSeq, 10, 4);
                            }
                            
                        // PAYLOAD FOLLOWS HERE
                        LNAV.TIME  = PortRead(port, 0) << 0;
                        LNAV.TIME |= PortRead(port, 0) << 8;
                        LNAV.TIME |= PortRead(port, 0) << 16;
                        LNAV.TIME |= PortRead(port, 0) << 24;
                        LNAV.TIME |= PortRead(port, 0) << 32;
                        LNAV.TIME |= PortRead(port, 0) << 40;
                        // TIME SHOULD BE UNSIGNED BUT IS SIGNED HERE...
                        
                        tmp  = PortRead(port, 0) << 0;
                        tmp |= PortRead(port, 0) << 8;
                        tmp |= PortRead(port, 0) << 16;
                        tmp |= PortRead(port, 0) << 24;
                        LNAV.LAT = tmp*0.0000000419095;
                        
                        tmp  = PortRead(port, 0) << 0;
                        tmp |= PortRead(port, 0) << 8;
                        tmp |= PortRead(port, 0) << 16;
                        tmp |= PortRead(port, 0) << 24;
                        LNAV.LON = tmp*0.000000083819;
                        
                        tmp  = PortRead(port, 0) << 0;
                        tmp |= PortRead(port, 0) << 8;
                        tmp |= PortRead(port, 0) << 16;
                        tmp |= PortRead(port, 0) << 24;
                        LNAV.DEPTH = tmp/1000;
                        
                        tmp  = PortRead(port, 0) << 0;
                        tmp |= PortRead(port, 0) << 8;
                        LNAV.ALT = tmp/100;
                        
                        tmp  = PortRead(port, 0) << 0;
                        tmp |= PortRead(port, 0) << 8;
                        LNAV.ROLL = tmp*0.005493164;
                        
                        tmp  = PortRead(port, 0) << 0;
                        tmp |= PortRead(port, 0) << 8;
                        LNAV.PITCH = tmp*0.005493164;
                        
                        tmp  = PortRead(port, 0) << 0;
                        tmp |= PortRead(port, 0) << 8;
                        LNAV.HEADING = tmp*0.005493164;
                        
                        tmp  = PortRead(port, 0) << 0;
                        tmp |= PortRead(port, 0) << 8;
                        LNAV.VEL_NORTH = tmp/1000;
                        
                        tmp  = PortRead(port, 0) << 0;
                        tmp |= PortRead(port, 0) << 8;
                        LNAV.VEL_EAST = tmp/1000;
                        
                        tmp  = PortRead(port, 0) << 0;
                        tmp |= PortRead(port, 0) << 8;
                        LNAV.VEL_DOWN = tmp/1000;
                
                        tmp  = PortRead(port, 0) << 0;
                        tmp |= PortRead(port, 0) << 8;
                        LNAV.ANG_RATE_FWD = tmp/100;
                        
                        tmp  = PortRead(port, 0) << 0;
                        tmp |= PortRead(port, 0) << 8;
                        LNAV.ANG_RATE_STBD = tmp/100;
                        
                        tmp  = PortRead(port, 0) << 0;
                        tmp |= PortRead(port, 0) << 8;
                        LNAV.ANG_RATE_DWN = tmp/100;

                        tmp  = PortRead(port, 0) << 0;
                        tmp |= PortRead(port, 0) << 8;
                        LNAV.ACC_FWD = tmp/1000;
                        
                        tmp  = PortRead(port, 0) << 0;
                        tmp |= PortRead(port, 0) << 8;
                        LNAV.ACC_STBD = tmp/1000;
                        
                        tmp  = PortRead(port, 0) << 0;
                        tmp |= PortRead(port, 0) << 8;
                        LNAV.ACC_DOWN = tmp/1000;
                                                                                                                                    
                        tmp  = PortRead(port, 0) << 0;
                        tmp |= PortRead(port, 0) << 8;
                        tmp |= PortRead(port, 0) << 16;
                        tmp |= PortRead(port, 0) << 24;
                        LNAV.ERR_POS_MAJOR = tmp;  // HOW TO CONVERT TO FLOAT?
                        
                        tmp  = PortRead(port, 0) << 0;
                        tmp |= PortRead(port, 0) << 8;
                        tmp |= PortRead(port, 0) << 16;
                        tmp |= PortRead(port, 0) << 24;
                        LNAV.ERR_POS_MINOR = tmp;  // HOW TO CONVERT TO FLOAT?
                        
                        tmp  = PortRead(port, 0) << 0;
                        tmp |= PortRead(port, 0) << 8;
                        tmp |= PortRead(port, 0) << 16;
                        tmp |= PortRead(port, 0) << 24;
                        LNAV.ERR_POS_DIR = tmp;  // HOW TO CONVERT TO FLOAT?

                        tmp  = PortRead(port, 0) << 0;
                        tmp |= PortRead(port, 0) << 8;
                        tmp |= PortRead(port, 0) << 16;
                        tmp |= PortRead(port, 0) << 24;
                        LNAV.STD_DEPTH = tmp;  // HOW TO CONVERT TO FLOAT?
                                    
                        tmp  = PortRead(port, 0) << 0;
                        tmp |= PortRead(port, 0) << 8;
                        tmp |= PortRead(port, 0) << 16;
                        tmp |= PortRead(port, 0) << 24;
                        LNAV.STD_LEV_NORTH = tmp;  // HOW TO CONVERT TO FLOAT?
                        
                        tmp  = PortRead(port, 0) << 0;
                        tmp |= PortRead(port, 0) << 8;
                        tmp |= PortRead(port, 0) << 16;
                        tmp |= PortRead(port, 0) << 24;
                        LNAV.STD_LEV_EAST = tmp;  // HOW TO CONVERT TO FLOAT?
                        
                        tmp  = PortRead(port, 0) << 0;
                        tmp |= PortRead(port, 0) << 8;
                        tmp |= PortRead(port, 0) << 16;
                        tmp |= PortRead(port, 0) << 24;
                        LNAV.STD_HEADING = tmp;  // HOW TO CONVERT TO FLOAT?   

                        tmp  = PortRead(port, 0) << 0;
                        tmp |= PortRead(port, 0) << 8;
                        tmp |= PortRead(port, 0) << 16;
                        tmp |= PortRead(port, 0) << 24;
                        LNAV.ERR_VEL_MAJOR = tmp;  // HOW TO CONVERT TO FLOAT?
                        
                        tmp  = PortRead(port, 0) << 0;
                        tmp |= PortRead(port, 0) << 8;
                        tmp |= PortRead(port, 0) << 16;
                        tmp |= PortRead(port, 0) << 24;
                        LNAV.ERR_VEL_MINOR = tmp;  // HOW TO CONVERT TO FLOAT?
                        
                        tmp  = PortRead(port, 0) << 0;
                        tmp |= PortRead(port, 0) << 8;
                        tmp |= PortRead(port, 0) << 16;
                        tmp |= PortRead(port, 0) << 24;
                        LNAV.ERR_VEL_MINOR_DIR = tmp;  // HOW TO CONVERT TO FLOAT?
                        
                        tmp  = PortRead(port, 0) << 0;
                        tmp |= PortRead(port, 0) << 8;
                        tmp |= PortRead(port, 0) << 16;
                        tmp |= PortRead(port, 0) << 24;
                        LNAV.STD_VEL_DOWN = tmp; // HOW TO CONVERT TO FLOAT?
                        
                        tmp  = PortRead(port, 0) << 0;
                        tmp |= PortRead(port, 0) << 8;
                        LNAV.STATUS = tmp; // HOW TO INTERPRET THIS?
                        
                        break;   
                        }
                    }
                }
            }
        }   
        
    // loop until buffer is empty
    while(PortRead(port, 0) != -1)
        {
        }
    
    // Increment Sequence
    RxSeq++;
    }
 
As far as the unsigned time value is concerned, the .TIME element of your LNAV structure is probably a 64-bit, 8-bitye signed integer, your current code initializes the high seven bytes to zero (LNAV.TIME = ... << 0;) , and then fills in all but the highest two bytes in the following five statements (LNAV.TIME |= ...;). So bit 63, the sign bit, of LNAV.TIME will always be zero and the signed integer value will always be positive. That being the case, you might be able to ignore the signed/unsigned issue.
 
First you need to know if the 16bit integers are giving you the values you expect to see. This is because you need to know if the numbers you are getting have the same Endianess as the Redlion device:
If your 16bit numbers are right, then you are on a winner :)
Code:
// receive the current colour for a tag and then write back the new colour
//
int tagIndex;
int tagValue;

int bluemask = 0b111110000000000;
int greenmask = 0b000001111100000;
int redmask = 0b000000000011111;
int red, green, blue;
float Floatred, Floatgreen, Floatblue;
float convFac = 8.2258;

int not_set; // choice of wether to use the new colour
//int finalR, finalG, finalB;


// convert the received string in to a tag reference number
tagIndex = FindTagIndex(TagLabel);

// get the value of the tag
tagValue = GetIntTag(tagIndex);

// convert to Red, Green, Blue
// this ANDs together the start value and the mask and then shifts all the bits by x to the right
blue = (tagValue & bluemask) >> 10;
Floatblue = (float) blue * convFac + 0.5;
Internal.ColourPIcker.Blue = (int) Floatblue;

    
green = (tagValue & greenmask) >> 5;
Floatgreen = (float) green * convFac + 0.5;
Internal.ColourPIcker.Green = (int) Floatgreen;
    
red = tagValue & redmask;
Floatred = (float) red * convFac + 0.5;
Internal.ColourPIcker.Red = (int) Floatred;

// set the colour picker to the current tag colour
Internal.ColourPIcker.Colour = ColGetRGB(Internal.ColourPIcker.Red,Internal.ColourPIcker.Green,Internal.ColourPIcker.Blue);


not_set = ShowModal(System.ColourPicker);

// if they choose to use the new colour then transfer to the tag
if (not_set == 1) SetIntTag(tagIndex, Internal.ColourPIcker.Colour)

Redlion stores colours in 5 bit sections in a 16 bit number, normal RGB is 8 bit sections in a 32bit number. I needed to extract the 3 5 bit sections, so I wrote the above. I used a MASK and the AND function to clear out the 11 bits I wasn't interested in and, and then >> to move bits to the right. I was looking for 5 bit values, I think you will see that you could equally extract 1 bit values using a similar method.

The Float numbers were always going to be more difficult, again you need to know what you are expecting to see, before you can check where there is a problem. This will show you what a Float value looks like as a 32bit number:
Convert the Float you have and see if it matches the bits you are receiving.

The difference between a signed integer and an unsigned integer is that the largest value bit is used to give the whether a number is positive or negative. So unless you are unfortunate enough to be dealing with 16 bit numbers bigger than 32767, and 32 bit numbers bigger than 2,147,483,647 you don't have worry about only having signed integers in Crimson.
 
Here is an arithmetic approach to converting four integer bytes, LSByte-first, to a float.
Code:
float flt;
int tmp;
[...]

    flt = PortRead(port, 0) / 8388608.0;  // byte 0 scaled:  bits 0-7 of 24
    flt += PortRead(port, 0) / 32768.0;   // byte 1 scaled:  bits 8-15 of 24
    flt += PortRead(port, 0) / 128.0      // byte 2 scaled:
                                          // - bits 0-6:  bits 16-22 of 24,
                                          // - bit 7:  low bit 0 of base 2 exponent)

    tmp = PortRead(port, 0) * 2;          // byte 3:
                                          // - bits 0-6:  high 7 bits, 1-7) of base 2 exponent
                                          // - bit 7:  sign bit

    if (tmp > 0 || flt > 0.0) {           // flt != 0.0 if any byte is non-zero
        if (flt >= 1.0) { ++tmp; }        // low exponent bit is 1; flt is already >= 1.0
        else { flt += 1.0; }              // low exponent bit is 0; ensure flt >= 1.0
        if (tmp >= 256) {                 // bit 7 of byte 3 is 1; sign bit
            tmp -= 256;                   // remove from exponent
            flt = -flt;                   // flt is negative
        }
    }

    flt *= Math.pow(2.0, tmp-127);         // apply excess 127 of exponent
[/CODE]
 
Wow, thanks for all the help! This seem quite complicated, frustrating when i know something similar is so easy to achieve in languages like Python!

Anyhow, @BryanG, I can confirm my integers, both 16 and 32 bits are handled correctly and the resulting data is correct. I have confirmed this using a LNAV message simulator with known values as input. So Endianess should be correct, if I assume it's packed the same way for the floats..

With my current approach, just pointing the bytes to a float tag in RedLion, the tag result is a large integer number rather than a float. I would probably need to do some more processing. Perhaps I should try @drbitboy's approach, fi it really needs to be that complicated?

All the best,
Anton
 
the tag result is a large integer number rather than a float
Can you post the number that you get and the float that you are transmitting. If you play on the website I gave above, you will find that some combinations of bits will give you a large integer looking number. Is the Tag where you are receiving the number Internal or going to another device? How would you do the conversion in Python? I did use Python to convert Modbus data from a 4G Router that included location data,

Code:
decoder = BinaryPayloadDecoder.fromRegisters(latitude, byteorder=Endian.Big)
result = decoder.decode_32bit_float()
resultstring = str(result)
resultlat = resultstring[0:12]
but to be honest, the Redlion was far easier :) .
 
I would only do that if the approach in the first link in post #5 does not work.
I could not get this one to work, can you please give a code snippet with this implemented to the structure I currently have, below:
C#:
tmp  = PortRead(port, 0) << 0;
tmp |= PortRead(port, 0) << 8;
tmp |= PortRead(port, 0) << 16;
tmp |= PortRead(port, 0) << 24;
LNAV.STD_HEADING = tmp;  // HOW TO CONVERT TO FLOAT?

I was trying to add the float myFloatValue = *(float *)(&value); but RedLion does not seem to recognize this, can you help?

Screenshot 2024-03-05 085937.png
I'm going to give some examples of sent LNAV data and associated outputs, stay tuned...
 
Last edited:
Can you post the number that you get and the float that you are transmitting. If you play on the website I gave above, you will find that some combinations of bits will give you a large integer looking number. Is the Tag where you are receiving the number Internal or going to another device? How would you do the conversion in Python? I did use Python to convert Modbus data from a 4G Router that included location data,

Code:
decoder = BinaryPayloadDecoder.fromRegisters(latitude, byteorder=Endian.Big)
result = decoder.decode_32bit_float()
resultstring = str(result)
resultlat = resultstring[0:12]
but to be honest, the Redlion was far easier :) .
The whole LNAV data package (90bytes) would be unpacked to corresponding datatypes in three rows in Python:
Python:
packet_fmt = '<6s 3l H 2h H 9h 11f 2s'                          // Components of LNAV string
packet_unpack = struct.Struct(packet_fmt).unpack_from
s = packet_unpack(rawdata)                                      // Unpack rawdata to LNAV components stored in list 's'
 
OK, plan B. The thing that made Modbus data conversion easy was the Data Block that can be added to its communication port, you can't do that for Raw UDP/IP. So instead can you have two sets of data, one in Raw format and one translated, see the picture. It gains you the ability to manipulate the data if needed, in terms of byte and word swapping and may well solve the Float problem. You use the data in a Raw tag as the source for the Translated tag, second picture down.
 

Attachments

  • Raw and Translated UDPIP.JPG
    Raw and Translated UDPIP.JPG
    165.3 KB · Views: 7
[Update: @BryanG has already found this in a previous post. More detail can be found here; you will still have to do the word swap somehow, maybe the slave can be configured to do it.]

Perhaps this might help; something about a "Treat as" option?

My Google Fu is not finding anything about pointers, or unions, or structures for Red Lion Crimson. I know the ad copy says the Crimson programming language is C-like; maybe there is some other way to do this? I did find a Crimson function R64TOREAL (cf. here) that converts an array of integers into double-precision floating point, but that has a different number of exponent bits so it would not work here.

Did you try the mathematical approach? It's ugly, but it should work.
 
Last edited:

Similar Topics

I need a CQM1H-CPU51 to talk to my Red lion Da30D i just need to read the status of some I/O and maybe read some data registers but I cannot...
Replies
7
Views
1,160
good morning all I have several red lion da30d using crimson 3.0 on the controls network in the plant. I am thinking about getting the AB power...
Replies
3
Views
2,168
We have two Red Lion DA30D units that keep dropping off the network. They are configured as 10.4.7.1 and 10.4.7.2 they work for so long then just...
Replies
24
Views
12,452
While they came up quickly with a fix for the alarm date issue quickly I will have to drive around for a week or so, burning up a lot of fuel...
Replies
4
Views
267
From the Red Lion website, after some customer enquiries in the last week or so... Rev. March 25, 2024 [18:30] Just thought it might help a...
Replies
9
Views
279
Back
Top Bottom