Measuring hardware API and C# - best approach advice needed



  • So, in the "Measuring software for schools" thread I may have complained about the included software being dead slow and not very userfriendly.

    However, the manufacturer also provides a C# API with which I could connect to the devices. I haven't yet approached a Bluetooth connection to the devices but I can connect via USB just fine and also have figured out how to get actual measurements from the thingamajigs (though some still pose a bit of a problem - the 3-axis g-sensor reports a total g-Force of 48 m/s² when lying still which for obvious reasons can't be quite right. There is a tare-function but that seems to do nothing. Oh, well...)

    Now, here's my problem: To do a measurement, you choose one of two approaches - you either cassyios.DoSingleMeasurement(); which then plonks the values into the selected measuring channels once or you do multiple measurements like this

    quantityUA.Selected = true;                // selects the channel - this would be channel A doing Voltage
    cassyios.OscilloscopeInterval = 0.001:     // interval of 1 ms between measurements, can go down to 1 microsecond
    cassyios.OscilloscopeCount = 200;          // amount of measurements taken
    while(!cassyios.DoOscilloscope())
        for(int i = 0; i < quantityUA.Values.Count; i++)
            Console.WriteLine("t={0} s\tUA={1} V", cassyios.Times[i], quantityUA.Values[i]);
    

    Now, as you can see, the whole thing delivers multiple measurements to me only after the DoOscilloscope() has finished.

    For obvious reasons, I'd really like it if I had a routine that doesn't block the thread (so I can update the UI) while still being precise.

    My first idea would be to offload the whole measuring mechanism into a worker thread but then I stumbled over the little problem that the available timers only go down to 1 ms (and I don't know how precise that would even be at 1 ms).

    Btw, they also included a complete documentation on how to talk to the devices directly through USB but that's an option I'd really like to avoid :)


  • FoxDev

    Are you working in C#4? If so, async that BITCHCOMPLAIN!



  • @RaceProUK said:

    Are you working in C#4? If so, async that <del>BITCH</del><ins>COMPLAIN</ins>!

    I'm working with C# 4.5 - but the API is from ye olden 2.0 days :)


  • FoxDev

    Then wrap the API in your own async method(s) ;)



  • Let's see if it will eat that.



  • @Rhywden said:

    My first idea would be to offload the whole measuring mechanism into a worker thread

    Go for it.

    @Rhywden said:

    but then I stumbled over the little problem that the available timers only go down to 1 ms (and I don't know how precise that would even be at 1 ms).

    I don't understand how this affects your worker thread idea. How are timers involved whatsoever in creating a worker thread? (Note: the thread could just store its own measurements as they come in at 1 ms intervals, then spit-them-out as-needed. Is that the confusion?)

    I'd also like to note there's no reason you can do this in .net 2. You don't need async/await to simply put blocking operations like that into a worker threads-- pretty sure worker threads are in .net 1.1, or perhaps even 1.0. Sure you have to type a little more boilerplate, but it works fine.



  • Sorry, I didn't make myself clear: It doesn't affect the thread idea per se. It was just an idea of how to avoid having to use the oscilloscope-function which (at least in the examples) only yields results when it's done. (And for some measurements, 1 ms is too slow. However, as the usual timer solutions don't provide high precision anyway, I have to use the osco-function anyway.)

    Whereas I'd like to display results as they come in (like an actual oscilloscope does).

    However, now that I think about it, it might very well be that the osco-function simply deposits its measurements into the quantity.Values array which in turn means that if I offload the osco-function into a worker thread, I then could simply read those values, say, every 500 ms and display all the new values.



  • @Rhywden said:

    Whereas I'd like to display results as they come in (like an actual oscilloscope does).

    Right; but the monitor's only running 60 FPS or so.

    @Rhywden said:

    However, now that I think about it, it might very well be that the osco-function simply deposits its measurements into the quantity.Values array which in turn means that if I offload the osco-function into a worker thread, I then could simply read those values, say, every 500 ms and display all the new values.

    Even if their function doesn't, there's nothing stopping you from implementing this array/cache in your own function.



  • Reading from the same array that another thread's writing to sounds bad. I'd give the worker thread a loop that alternately calls DoOscilloscope then copies the values to an array and posts them back to the main thread. Of course the big question is: do unread values get discarded i.e. while you're doing the copying and sending, are you creating a gap in the data?

    Also, 500ms will be too laggy. Obviously 16ms would be ideal, but 100-200 should be ok, if that's what you can get.



  • Am I mis-reading...the posted code will continually output however many values are available when DoOscilloscopereturns false, and then call DoOscilloscope again, repeating the process until it returns true.....

    If DoOscilloscope returns true on the first call, there will be no output...
    If DoOscillosope returns false forever, then the program will hang...

    (It would be totally different if there was a "{}" at the end of the while, but that is not what the indenting shows...



  • Yeah, I'm a bit weirded out by that as well but it's straight from the docs.



  • Gah. I finally figured out almost everything (even if the docs were brief to the point of non-existence and the hints included to the DLL were sometimes less than helpful) and was now trying to integrate it all into a proper UI.

    And by proper UI I mean: A user interface which does one task and does it well, not this everything-and-the-kitchen-sink-approach which infuriates you even if you know what you're doing.

    So I turned to create a Windows App - only to discover that the morons hardcoded references to System.Windows.Forms into the dll (which was supposed to deal with the measuring stuff only!)

    Thanks a lot.

    Of course I wrote them nicely and asked them to remove all UI-related calls from the .dll - but if that doesn't yield a positive reply I probably would have to look at decompiling the dll and removing/altering the offending functions myself?



  • Okay, just for shits'n'giggles, I ran the dll through a decompiler.

    dotPeek was free and seems to have managed some - only to run into UTF8 issues. Visual Studio really doesn't like stuff like:

    using \u0000a

  • FoxDev

    What does Telerik's JustDecompile make of it? That's free too. I think ILSpy is still a thing too.



  • ILSpy's website was down. Will try the other one.


  • FoxDev



  • @Rhywden said:

    Of course I wrote them nicely and asked them to remove all UI-related calls from the .dll - but if that doesn't yield a positive reply I probably would have to look at decompiling the dll and removing/altering the offending functions myself?

    I'm guessing they probably make pretty extensive use of them... it might be hard to find alternatives.



  • @blakeyrat said:

    I'm guessing they probably make pretty extensive use of them... it might be hard to find alternatives.

    Couldn't they simply extract the UI related stuff and put it into a secondary dll?



  • @Rhywden said:

    Couldn't they simply extract the UI related stuff and put it into a secondary dll?

    I dunno, probably.

    And I might be way off-base here, I'm working on the logic of "they wouldn't have added a reference to WinForms in a class library unless it solved a really big problem for them..." which might be false.

    They might have just added a reference to WinForms because they're idiots.



  • Well, it looks like this:

    The dll provides an interface between the USB measuring devices and C#. allowing you to set stuff like channels, frequencies and other things easily instead of having to push bits directly over serial (if you want you can do that, too, the API doc provides 6 freaking pages detailing exactly which bits to flip for what setting - but explaining the actual API in this detail? Oh no!) That's what 95% of the functions, classes and whatever are dealing with.

    The offending functions? Half of them deal with painting a picture of the device into the window, allowing you to set the channel by clicking. One of the functions is "paintDevice()", one is "mouseButtonClicked()".

    The other functions? They're useful if you want to draw mathematical functions into a textbox - it looks like some variant of MathML.

    Both of them are very clearly UI stuff and have nothing to do with the measuring hardware on itself.



  • Alas, ILSpy also has some problems with the encoding and produces non-usable stuff.

    And, nope, Telerik does the same thing.

    then again, the DeObfuscator plugin of Telerik seems to have done the trick... nasty guys.


  • Discourse touched me in a no-no place

    @blakeyrat said:

    because they're idiots

    Let's save some time and go with that. It might not be true, but it involves the fewest assumptions…



  • So, now I have to contend with the problem that they used Mono for event polling. In particular, it's the Mono.Posix namespace that's giving me troubles since I haven't figured out how to reference that one in a normal Visual Studio context. And, yes, I have Mono for Windows installed and the MonoPosixHelper.dll is indeed existing.

    The naive approach of simply referencing the dll yields a "Is this a proper assembly? Because I can't use that!" by VS.

    Also, I haven't yet been able to grasp why they obfuscated the dll in the first place.


  • FoxDev

    `Mono.Posix' is a NuGet package. If you can, I'd install that; it should automatically add the correct references for you ;)



  • Okay, I simply removed the Mono references (and the classes it's called in) because they were dealing with Linux and MacOS file system access which I'm not interested in anyway.

    Trying to compile the code then yielded two errors. The first one I'm probably a bit to inexperienced to make heads or tails of:

    public string LastMessage {
        get { return get_LastMessage(); }
    set { set_LastMessage(value); }
    }
    
    public string get_LastMessage() {
        return base.Input.lastMessage;
    }
    
    public void set_LastMessage(string value) {
        base.Input.lastMessage = value;
    }
    

    VS objects to the first two lines on the basis of: Error 17 Type 'LD.Api.IOs.Box524078.Can524078' already reserves a member called 'get_LastMessage' with the same parameter types (same applies to set_LastMessage)

    Then the compiler complained about a syntax error in another file - a closing bracket in the wrong place.

    And what I found makes it an official WTF:

    if ((j & 1) == 0)
    {
       goto Label1;
    }
    nums.Add(num2);
    goto Label0;
    

    @RaceProUK said:

    `Mono.Posix' is a NuGet package. If you can, I'd install that; it should automatically add the correct references for you ;)

    Thanks, but I simply cut it out. Don't need to write to files on Linux anyway :)


  • Discourse touched me in a no-no place

    @Rhywden said:

    VS objects to the first two lines on the basis of: Error 17 Type 'LD.Api.IOs.Box524078.Can524078' already reserves a member called 'get_LastMessage' with the same parameter types (same applies to set_LastMessage)

    I'm not positive, but perhaps you have to declare LastMessage to be public override?



  • @FrostCat said:

    I'm not positive, but perhaps you have to declare LastMessage to be public override?

    I simply deleted the whole class since it dealt with hardware I don't possess (and won't ever possess). However, fixing that one made a whole slew of other errors crop up.

    In short, I've given up on that approach and am now indeed trying to talk to the device directly over USB.

    However, that also has been a somewhat epic journey. The first article I stumbled upon when searching on how to talk to USB devices in Windows Apps told me to use the Windows.Devices.Usb.
    However, for that one the device actually has to load the WinUSB driver instead of the bog standard HID device driver.

    Not that the device manager is telling which one it loaded, mind. (Hint: It's not WinUSB)

    But there's still the Windows.Devices.HumanInterfaceDevice namespace! And it even found the device! Yay!

    Now I just have to find out what kind of Usage Page and Usage Id I have to define in order to be able to send and receive data. Or if I even need that definition. And where I'm getting the hex values for those Usage Pages from.

    And which function actually sends and receives data. The descriptions are a bit vague.


  • FoxDev

    USB is truly TR :wtf:



  • Yeah, I'm having fun here :)



  • Okay, I'm sending data successfully to the device (at least I think so), but as of yet I have to receive anything else but a bunch of zeros in return (even when sending the commands which should simply return the firmware version).

    oh, well. Another mystery for tomorrow.

    And the source code for the dll is chockfull of case-switch and goto-commands, by the way.


  • Discourse touched me in a no-no place

    @Rhywden said:

    I simply deleted the whole class since it dealt with hardware I don't possess (and won't ever possess).

    Well, that works (but not in your case, as you say next.) My guess, without knowing the library, is that something along those lines was the problem.

    @Rhywden said:

    However, "fixing" that one made a whole slew of other errors crop up.

    FTFY. 😄

    @Rhywden said:

    However, that also has been a somewhat epic journey.

    You might have been better off P/Invoking a native DLL.


  • FoxDev

    @FrostCat said:

    You might have been better off P/Invoking a native DLL.

    That's a damn good idea; that's what their API DLL will be doing anyway 😄



  • @FrostCat said:

    FTFY. 😄
    You might have been better off P/Invoking a native DLL.

    Unrelated errors, I might add. Looked to me like VS simply didn't throw every single error in my face, instead opting for: "Oh, you fixed that one? Yeah, here are some more!"

    And p/invoking a native dll? Yeah, they're doing that indeed but there's the small problem that they're doing it in order to talk to their devices, throwing bits and bytes around. And that's what I originally wanted to avoid because it means that I probably have to calculate some values by myself.

    For instance, one of the devices for measuring distances is simply a light barrier with a spoke wheel - the frequency of dark/light cycles determines speed and the number of cycles determines distance.



  • @Rhywden said:

    And the source code for the dll is chockfull of case-switch and goto-commands, by the way.

    The source, or the de-compiled source?



  • @Rhywden said:

    VS objects to the first two lines on the basis of: Error 17 Type 'LD.Api.IOs.Box524078.Can524078' already reserves a member called 'get_LastMessage' with the same parameter types (same applies to set_LastMessage)

    It's the decompiler's fault - it somehow managed to write both the property and its individual get & set methods, having the property's accessors call those. (yes, once you create a property XYZ, the compiler automatically creates get_XYZ and set_XYZ methods (if corresponding accessors exist) and gives an error if you try to define them yourself - try it.
    To fix, just rename the individual methods or move their content to the property accessors, or just use a better decompiler.

    @Rhywden said:

    Then the compiler complained about a syntax error in another file - a closing bracket in the wrong place.

    Again a decompiler's fault. Sounds easy enough to fix, though?

    @Rhywden said:

    if ((j & 1) == 0)
    {
    goto Label1;
    }
    nums.Add(num2);
    goto Label0;

    Again the decompiler's fault, though converting gotos (how all ifs/loops are stored in the bytecode) to ifs and loops is not that easy, so you can't fault it too much for missing an obscure case or two.

    You seem to be expecting a decompiler to magically turn your assembly into the original source code - that's not going to happen. At best, it can produce source code that will behave the same as the assembly once compiled.



  • @blakeyrat said:

    The source, or the de-compiled source?

    Yes, the decompiled one. Yeah, you're both right (@CreatedToDislikeThis), should've seen this. Then again, I'm a bit new to this decompiling business.

    On the other hand, I think I'll talk to the device itself by pushing bytes around and simply use the decompiled stuff to get a hold on any magic numbers I might need. I've installed a USB protocol listener so I can capture exactly what the original API pushes out and what values it receives in return, so in conjunction with their documentation it shouldn't be impossible to retrace their steps exactly.

    And, yes, I figured out why I received only zeroes. You have to register an event handler which deals with incoming data after you sent some, of course. It's perfectly logical that a device will return data immediately after I request it instead of deigning to wait until I invoke an InputReport... I probably shouldn't try to figure out new stuff at 3AM.


  • Discourse touched me in a no-no place

    @Rhywden said:

    "Oh, you fixed that one? Yeah, here are some more!"

    That's always fun.

    Kinda odd, though; it used to give you up to 100 + almost any number of warnings before quitting.



  • @FrostCat said:

    That's always fun.

    Kinda odd, though; it used to give you up to 100 + almost any number of warnings before quitting.

    Fun thing was that it gave me a mere 2 errors in the beginning - when I fixed those, it blossomed into 30 new ones, all completely unrelated to the previous errors.

    Strangely, that somehow coincided with my decision to throw in the towel.


  • Discourse touched me in a no-no place

    @Rhywden said:

    Fun thing was that it gave me a mere 2 errors in the beginning - when I fixed those, it blossomed into 30 new ones, all completely unrelated to the previous errors.

    Oh, those are FUN. Many years ago I tried to take an old Unix networked game and get it working under Windows, without knowing much about the architectural differences. Ran into a lot of that.

    "Hey, why are all the stars tucked in to one corner of the map? Oh, because this code uses an RNG that produces 48-bit numbers for coordinates, and they didn't bother using anything but the default 16-bit RNG for Windows."



  • So, yeah, a stripped down api.dll is a no-show.

    Then again, by writing everything from scratch without even touching their API, I'm also able to circumvent any licensing timebombs they might have included.

    Like, the fact that the documentation wishes one to "have much fun developing stuff for the devices" but does not mention under which type of license one would actually use the API. BSD? Apache? GNU? Something else way more restrictive?



  • And the developer finally answered some of my questions. Well, sort of. Since I now have to push bytes over the connection, I asked him several question like:

    "How do I determine which measuring device is connected to the USB converter?"
    "How do I actually start a measurement?"
    "How do I do a one-off measurement (i.e. single value) instead of measurements over a period of time?"
    "How do I measure the time between two events when using light barriers?"

    He choose to answer the latter question only. By using the source code from the goddamn API! Which, of course, uses freaking enums which, of course, I don't have access to.

    I love people who only answer one question when being confronted with several. And then don't actually answer the question to boot.

    To give you an inkling of what the documentation looks like:

    I mean, yeah, you can make out what is supposed to go where but it's not exactly "figure out at a glance" stuff.


  • BINNED

    Eh, at least it's not in Engrish.

    I know there are some proper gems in there but, honestly, I remembered what pain that thing was and I can't make myself read any more of it right now.



  • Okay, I need another two pieces of advice for issues I stumbled over.

    1. First one is relatively simple: From analyzing the USB traffic it seems that the software routine asks for current data points at a frequency of once per millsecond. So I construct a buffer, detach it from the dataWriter and send the outReport. However, the incoming data needs to be handled by an event handler since the outReport-Routine has no way to listen for a response (an inReport simply yields a lot of zeroes).
      The incoming data also needs to be analyzed for validity since not all data packets contain actual data (but can contain the equivalent of "nothing new happened since your last request").
      Is C# fast enough for that or do I need to look at C++, considering the millisecond requirement?

    2. A successful measurement consists of roughly the following procedure:

    • Send Command 1

    • Get response 1

    • Send Command 2 with parameters a and b

    • Get response 2

    • Send Command 3 with parameter C

    • No response

    • Send Command 4 in a loop

    • Response 4 in a loop. If byte 4 of the response is zero, break out of the loop (i.e. waiting for trigger signal)

    • Send Command 5 in a loop

    • Response 5 in a loop. Needs to be analyzed if it contains actual measurements. Break out of loop if needed (i.e. user pressed "Stop" or byte 4 is zero)

    • Send Command 6 (reset)

    • receiver Answer 6

      I'm a bit unsure on how to implement that one. My first idea was an array of Structs, with one property being the command itself and the second one determining whether the command gets an answer or if it's looped, sort of like this:

      struct Command
      {
      byte[] commandbuffer;
      int answer; //0 = no answer, 1=answer(one-off), 2=answer(loop)
      }
      Command[] listOfCommands;

    Okay, the "answer" thing would be an enum but you get the intention. Then either the CommandSending-Routine or the EventListener would call the next entry in the array (or loop it), depending on the requirements.

    Is this the proper way to go about it or is there something better?


  • FoxDev

    @Rhywden said:

    Is C# fast enough for that or do I need to look at C++, considering the millisecond requirement?

    Theoretically, yes; so long as the reading is in its own thread, you should be fine. There's no guarantee, but you'd have that same issue in C++ too.


    @Rhywden said:

    I'm a bit unsure on how to implement that one.

    Sounds a bit like you're describing a state machine; whether that's right or not, I'm not sure. But the struct idea sounds pretty decent ;)



  • @Rhywden said:

    Is C# fast enough for that or do I need to look at C++, considering the millisecond requirement?

    Call me Mr. Retard-o, but isn't the original DLL you're writing this all to replace in C#? So the answer would be "duh, yes?"



  • @blakeyrat said:

    Call me Mr. Retard-o, but isn't the original DLL you're writing this all to replace in C#? So the answer would be "duh, yes?"

    I'm not 100% sure it's all in C#. The majority is, yes, but the whole decompiled thing is positively labyrinthine.


  • Discourse touched me in a no-no place

    Think of it a different way: that fancy CPU you've got has got time to do a million operations or more between each of those millisecond polling calls. With ease. If that's not enough time to note down a few values in a nice big ring buffer (or however you're collecting the values) you're doing something horribly wrong…


  • BINNED

    The only concern is if he's doing some heavy maths with the results, but that would happen with any language. I don't know enough about .NET event handling, but if it's parallelized it shouldn't be a problem.

    Offtopic: I love my new spellchecker settings:


  • Discourse touched me in a no-no place

    @Onyx said:

    The only concern is if he's doing some heavy maths with the results, but that would happen with any language.

    That shouldn't happen in the data collector; put it in another thread and let the OS do most of the worrying (since most CPUs have multiple cores now, that should be OK).


  • Java Dev

    Yeah, for time-sensitive data collection you want to get an isolated thread, push priority to max, and stuff the data into a ring buffer ASAP.


Log in to reply