0

I have a device that outputs various sensor data, the guy that made the firmware sent me a "sample response" and what it means, basically it has a SOF hex key, a EOF hex key and an ESC key that is added XOR to avoid confusions when the same hex value needs to be "in the middle". Now from that gigangic 0x... string that is my "sample response" each "value" is represented by 4 bytes (8 chars) which is why I made a method in which it's adding each 8-chars into an array and removing it from the received response, each 8-byte set goes into an array and then gets "translated", however from what I was told every number should be translating roughly to 0.24xxxx So at the end I should end up with a 50-sensor array all between 0.2 and 0.3, however when I parse this data my output shows values ranging from 1.95E-37 to 265209.9 . . .which is like completely crazy.

Currently I know my code sucks and as I am thinking more about it I feel I am completely ignoring that ESC key that is there fo something, I assume I should be parsing each byte as it comes rather than "grouping it" first? but I am at a loss on how to even arrange the data.

    readonly string SOF = "0x02";
    readonly string EOF = "7E";
    readonly string ESC = "2D";

    private void testParsingBtn(object sender, EventArgs e)
    {
        string exResponse = "0x0200C800013E7F4A643E7F766E3E7F8DFC3E7F6C603E7F59DC3E7F60663E7F57903E7F5F823E7F502E3E7F60303E7F612C3E7F61683E7F43923E7F4ABE3E7F7CDA3E7F5D783D392F383E7F4AF43E7F68703E7F59BE3E7F531C3E7F60783E7F62A03E7F6C903E7F7A883E7F4D523E7F4E783E7F3A983E7F6FBA3E7F52AA3E7F6D983E7F6F543E7F6AE03D392A703E7F750C3E7F6C423E7F64A43E7F4F323E7F61863E7F60A83E7F711C3E7F81483E7F68E83E7F56163E7F732C3E7F6CA83E7F65823E7F72963E7F668A3E7F48C0C05C7E";
        parseResponse(exResponse);
    }
    private void parseResponse (string receivedString)
    {
        String[] receivedBytes = new String[50];

        if (receivedString.Substring(0,4) == SOF)
        {
            Console.WriteLine("Start of File (SOF) detected");
            receivedString = receivedString.Remove(0, 4);
        }

        if (receivedString.Substring(receivedString.Length - 2, 2) == EOF)
        {
            Console.WriteLine("End of File (EOF) detected");
            receivedString = receivedString.Remove(receivedString.Length-2, 2);
        }

        //Convert the Hex to decimal and set it to the probe array and update the GUI
        for (int i = 0; i < receivedBytes.Length; i++)
        {
            receivedBytes[i] = receivedString.Substring(0, 8);
            receivedString = receivedString.Remove(0, 8);
        }

        foreach (String str in receivedBytes)
        {
            Console.WriteLine(str);
        }

        
        for (int i = 0; i < probeArray.Length; i++)
        {
            byte[] arr = StringToByteArray(receivedBytes[i]);
            probeArray[i].Temp = BitConverter.ToSingle(arr,0);
        }
        foreach (Probe probe in probeArray)
        {
            Console.WriteLine(probe.Temp);
        }
        updateTemps();
    }


    private static byte[] StringToByteArray(string hex)
    {
        return Enumerable.Range(0, hex.Length)
                         .Where(x => x % 2 == 0)
                         .Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
                         .ToArray();
    }
}
  • Should I convert it into individual bytes then? (2 chars?) without grouping them in 8-byte arrays? Is that what you mean? (I feel I need to check every char fro whether its a SOF/ EOF/ESC, but I'm only "hard-coding" the removal of the tail and beginning) – David Boydston Mar 22 '22 at 21:56
  • OK I see now, what does he ESC mean>> ESC key that is added XOR to avoid confusions when the same hex value needs to be "in the middle". can you clarify – pm100 Mar 22 '22 at 22:10
  • BTW - usefull tool https://gregstoll.com/~gregstoll/floattohex/ – pm100 Mar 22 '22 at 22:17
  • first my guess is that c800 is the length not including header an trailer c800 = 200, you data is 208 bytes, still working on the rest though – pm100 Mar 22 '22 at 22:26
  • the first float starts at offset 5 => 3e7f4a64 = 0.249307, thats clearly a header 0200 c800 01 to start – pm100 Mar 22 '22 at 22:31

1 Answers1

2

Here you go

internal class Program {
    static float decode(string hex) {
        var k = Enumerable.Range(0, hex.Length)
               .Where(x => x % 2 == 0)
               .Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
               .ToArray();

        Array.Reverse(k);
        return BitConverter.ToSingle(k, 0);
    }

    static void Main(string[] args) {

        string hex = "0x0200C800013E7F4A643E7F766E3E7F8DFC3E7F6C603E7F59DC3E7F60663E7F57903E7F5F823E7F502E3E7F60303E7F612C3E7F61683E7F43923E7F4ABE3E7F7CDA3E7F5D783D392F383E7F4AF43E7F68703E7F59BE3E7F531C3E7F60783E7F62A03E7F6C903E7F7A883E7F4D523E7F4E783E7F3A983E7F6FBA3E7F52AA3E7F6D983E7F6F543E7F6AE03D392A703E7F750C3E7F6C423E7F64A43E7F4F323E7F61863E7F60A83E7F711C3E7F81483E7F68E83E7F56163E7F732C3E7F6CA83E7F65823E7F72963E7F668A3E7F48C0C05C7E";
        for(int i = 12; i < hex.Length - 12; i+= 8) {

            var f = decode(hex.Substring(i, 8));
            Console.WriteLine(f);
        
        }
    }
}

output

0.24930722
0.24947521
0.24956506
0.24943686
0.24936622
0.24939117
0.24935746
0.24938777
0.2493293
0.24939036
0.24939412
0.24939501
0.2492812
0.24930856
0.24949971
0.24937999
0.045211047
0.24930936
0.24942183
0.24936578
0.24934047
0.24939144
0.24939966
0.24943757
0.24949086
0.24931839
0.24932277
0.24924695
0.24944964
0.24933878
0.2494415
0.24944812
0.24943113
0.045206487
0.24946994
0.24943641
0.24940735
0.24932554
0.24939546
0.24939215
0.24945492
0.2495166
0.24942362
0.24935183
0.24946278
0.24943793
0.24941066
0.24946055
0.2494146
0.24930096

Note the array.reverse, apparently BitConverter.ToSingle reads backwards

The order of bytes in the array must reflect the endianness of the computer system's architecture.

did not know 'endianness' counted for floats too


How does that string to binary conversion thing work?

Its a LINQ pipeline, to become a competent c# programmer you need to know LINQ.

It works like this

IEnumerable<T>->F1()->F2()->F3....

The Fns are extension methods defined on IEnumerable<T> that return an IEnumerable<T>.

IEnumerable<T> is someting you can do foreach(....) on. like

  • arrays
  • List
  • Stack
  • A string (enumerate the chars)
  • Dictionary<T1,T2>
  • ....

The code for hex to binary is arguably not a very good use of LINQ (if you look at the comments on the answer that you copied it from you will see a lively debate about it).

Here is a simple example.

We have a class of Employee

public class Employee {
    public string Name;
    public string ID;
    public int Salary;
}

and a list of them

 var emps = new List<Employee>() {
            new Employee{Name ="Simon", ID="x1", Salary= 500},
            new Employee{Name ="Sarah", ID="g4", Salary= 600},
            new Employee{Name ="Adam", ID="h4", Salary= 700}

        };

now we want a sorted list of the names of emps earing > 500

        var names = emps.Where(e => e.Salary > 500).Select(e => e.Name).OrderBy(s => s).ToList();

'emps' is our source IEnumerable

  emps=>Where

Where filters based on its lambda predicate argument

  Where=>Select

Select 'projects' the input to the output, you transform the entry via the lambda. In this case the input employee 'e', is converted to a string, e.Name

  Select=>OrderBy

OrderBy sorts on the key selected by the lambda, in this case the string 's' (ie the name)

  OrderBy=>ToList

The pipline has lost its Listness its just passing one Item at a time, we need to materialze it back to a useful container. ToList makes it into a List (becuase thats what came out of OrderBy)

There re two types on LINQ functions, those that are

 IEnum->F->IEnum

Then can be pipleined together, plus

IEnum->F->One thing

These terminate the chain

ToXXX functions terminate the chain

Also others like Count

   var richCount = emps.Where(e => e.Salary > 500).Select(e => e.Name).OrderBy(s => s).Count();

Gives us a count of emps with salary > 500 (the select and orderby are useless here). Simpler is

   var richCount = emps.Count(e => e.Salary > 500);

Count can take a predicate.

If you want to see how these things work look here

https://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,e449fbc07f49dc52

LINQ is amazingly useful and should be in your c# toolbox. Anytime you feel youself reachin for a for llop over some data, think LINQ. Its very efficient and all c# devs that come after you will get waht you are doing


OK LINQ tutorial over, how does tha hex string -> byte array work

 var k = Enumerable.Range(0, hex.Length)
               .Where(x => x % 2 == 0)
               .Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
               .ToArray();

We need an IEnumerable to start a LINQ pipeline. We have a string but the only thing we can easily do with that is enumerate each character, we want pairs, so writer chose a different tactic

Enumerable is a class of static generator methods, they generate IEnumerables see https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable?view=net-6.0

Enumerable.Range generates a sequence of numbers, in this case 0 to 8 (hex string is 8 chars)

 Range->Where

The Where selects only the even numbers

 Where->Select

The select says

  x => Convert.ToByte(hex.Substring(x, 2), 16)

ie map int input number (0,2,4,6) to that substring, convert combo

Finally

  ToArray()

To get an array of bytes

pm100
  • 48,078
  • 23
  • 82
  • 145
  • Little vs big endian? Bitconverter is unreliable when talking to embedded platforms. – 3Dave Mar 22 '22 at 22:47
  • @3Dave well OP said values lie between 0.2 and 0.3 , so I think i got the values correct (did not have the reverse in first go) – pm100 Mar 22 '22 at 22:49
  • Oh, your results definitely look correct. I was just thinking about why the reverse was necessary. Every modern processor uses little endian, but some Arm devices can do both. Shrug. – 3Dave Mar 22 '22 at 22:49
  • 1
    @3Dave because the transmission was sent big endian and my windows box is little endian – pm100 Mar 22 '22 at 22:51
  • As it would be if it was Windows or Linux, a phone, console, etc. Ancient Motorola MCUs use big endian, but... not much else produced this century. We force things to little endian when sending over a socket, SPI, etc. It's an annoying necessity. – 3Dave Mar 22 '22 at 22:52
  • @3Dave "We force things to little endian when sending" - big endian is the network standard. BIg endian is the human way of writing things so maybe thats why – pm100 Mar 23 '22 at 16:31
  • Holy guac bro, I am trying to wrap my head around this, this is the first time I've used C# (coming from Swift and Java). If you dont mind, could you elaborate on the whole Enumerable statement? What does (x must be equal or greater than the remainder of x/2 ? and must equal 0?) not too familiar with predicates (which I assume are like lambda statements?) – David Boydston Mar 23 '22 at 16:36
  • @DavidBoydston I lifted that code straight from you, you probably found it by googling. Its pretty fancy and not how I would have done it. I will edit into my answer a) how I would have done it b) how the Enumerable code works (later) – pm100 Mar 23 '22 at 16:40
  • @DavidBoydston THat aside, please tick the answer as correct since I did decode that string for you – pm100 Mar 23 '22 at 16:40
  • @DavidBoydston here is probably where you got it from https://stackoverflow.com/questions/321370/how-can-i-convert-a-hex-string-to-a-byte-array – pm100 Mar 23 '22 at 16:42
  • Oh damn, didnt even notice haha. Thanks! that reverse thing is also another wall I was probably gonna crash into soon enough. – David Boydston Mar 23 '22 at 16:54
  • Thanks for the LINQ crash course @pm100 – David Boydston Mar 23 '22 at 21:12