|
Let's assume your server was renamed ZVERSA (or MYJSONMACHINE) and you start getting the string
ENAMEZVERSAJSON9000UUID$0b5a576d-a276-VERS-a9e8-134c41133102VERS7.9.2
^^^^
And now you try to get the version (or the json) field...
|
|
|
|
|
I agree with you totally Luc but I can't see a better way of parsing the string - if you have any suggestions please post them
"We can't stop here - this is bat country" - Hunter S Thompson - RIP
|
|
|
|
|
Sure, here is some C#-like pseudocode that scans the line from left to right:
private Dictionary<string,string> parseLine(line) {
dict=New Dictionary<string,string>;
while not at end of line
key=readString(4)
length=readInt(1)
value=readString(length)
dict[key]=value
repeat
return dict
}
Afterwards you can easily access the values like so: dict["VERS"] ; beware they are all stored as strings, when a number is required, you'd have to apply int.TryParse() or similar while pulling them from the dict.
|
|
|
|
|
 Hi Luc, I was just going to post what I've done and I saw your reply - anyway here it is
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LMS
{
public class ParseLMSResponse
{
private const string NAME = "ENAME";
private const string PORT = "JSON";
private const string UUID = "UUID";
private const string VERS = "VERS";
private byte[] ResponseBytes { get; set;}
private Dictionary<string, string> KeyValuePairs { get; set; }
public ParseLMSResponse(byte[] ResponseBytes)
{
this.ResponseBytes = ResponseBytes;
this.GetKeyValuePairs();
}
private void GetKeyValuePairs()
{
string keyname = "";
string keyvalue = "";
int i = 0;
byte b;
int DataLen = 0;
KeyValuePairs = new Dictionary<string, string>();
while (i < ResponseBytes.Length)
{
keyname = "";
keyvalue = "";
b = ResponseBytes[i];
while (b >= 65 && b <= 90)
{
keyname += Convert.ToChar(b);
++i;
b = ResponseBytes[i];
}
DataLen = Convert.ToInt32(b);
++i;
keyvalue = Encoding.UTF8.GetString(ResponseBytes, i, DataLen);
KeyValuePairs.Add(keyname, keyvalue);
i += DataLen;
}
}
public string GetHostName
{
get
{
return GetValue(NAME);
}
}
public string GetUUID
{
get
{
return GetValue(UUID);
}
}
public string GetVersion
{
get
{
return GetValue(VERS);
}
}
public int GetPortNumber
{
get
{
return Convert.ToInt32(GetValue(PORT));
}
}
private string GetValue(string Name)
{
string RetVal = "";
KeyValuePairs.TryGetValue(Name, out RetVal);
return RetVal;
}
}
}
A much better way methinks - thanks for your help
"We can't stop here - this is bat country" - Hunter S Thompson - RIP
|
|
|
|
|
OK, you've got it!
|
|
|
|
|
 Hi Pete,
while your last message keeps showing up and disappearing again, I changed your code a bit to be more concise and better match C# conventions:
using System;
using System.Collections.Generic;
using System.Text;
namespace LMS {
public class LMSResponseParser {
private byte[] responseBytes;
private Dictionary<string, string> dict;
public LMSResponseParser(byte[] responseBytes) {
this.responseBytes=responseBytes;
}
public string HostName {
get {
return getValue("ENAME");
}
}
public string UUID {
get {
return getValue("UUID");
}
}
public string Version {
get {
return getValue("VERS");
}
}
public int PortNumber {
get {
return Convert.ToInt32(getValue("PORT"));
}
}
private string getValue(string key) {
try {
if (dict==null) parse();
return dict[key];
} catch (Exception exc) {
throw new ApplicationException(
"Error parsing LMS response '"+responseBytes+"'", exc);
}
}
private void parse() {
dict=new Dictionary<string, string>();
for (int i = 0; i<responseBytes.Length;) {
string key = "";
byte b = responseBytes[i++];
while (b>='A'&&b<='Z') {
key+=(char)b;
b=responseBytes[i++];
}
int len = b;
string value = Encoding.ASCII.GetString(responseBytes, i, len);
i+=len;
dict[key]=value;
}
}
}
}
What I changed:
- usage slightly different: create an LMSResponseParser then get its properties; no reuse, parsing the next LMSresponse will require a new parser object!
- public variables, properties, method names start with uppercase, locals/privates don't.
- class names don't start with a verb, think "object" not "action".
- property names don't start with a verb (except sometimes "Is"); think "characteristic".
(OTOH Java does not have properties, there one uses getXxx() and setXxx() methods.)
- no internal properties (such as your ResponseBytes), there was no need to make them properties.
- no advance declaration of local variables, declare on first use instead (C# isn't C !)
- lazy parsing: only parse (once) when some result is actually asked for.
- error handling: what if an invalid set of bytes is given? or one of the keywords isn't present while trying to get its value? The above code catches it all and throws an exception.
PS: I did not test, there might be minor mistakes...
PS2: my way of placing { at the right rather than on the next line isn't what most people do, however I prefer it as it is more vertically compact.
|
|
|
|
|
Hi Luc, why did you remove the const strings ?
"We can't stop here - this is bat country" - Hunter S Thompson - RIP
|
|
|
|
|
I would surely use const statements at the start of a class if they were public, or were used more than once throughout the class, or the right values isn't sure yet and will probably need a change (that would deserve a comment of course!).
Your const names seem not to satisfy any of these conditions, and then I prefer to use their values right where they are needed, making it all more readable in my view.
And yes I made a little mistake in doing so, "PORT" should be "JSON" in the PortNumber property...
OTOH in your earlier code, where you had 4 and 5 for fieldname length and fieldname length + 1 I would have used either a const, or a variable set equal to fieldName.Length; that makes it more readable (less "magic"), as does my 'A' and 'Z' replacing your 65 and 90.
I prefer code to be both readable and compact (even when that may sound in conflict); both characteristics make it easier to understand and maintain code, leading to fewer errors over time.
|
|
|
|
|
I don't know why my message is disappearing but hey ho ( hamsters )
I think it just comes down to coding style and preference my old tutor used to tell me "get it working and then tidy it up" - I've been coding professionally for 40+ years so obviously I've developed my own style - I don't follow Microsoft's or anyone else's coding conventions unless I agree with them - but that's just me. Nice liaising with you.
"We can't stop here - this is bat country" - Hunter S Thompson - RIP
modified 6-Jul-20 13:42pm.
|
|
|
|
|
Of course we all have preferences and a personal style, and that is OK if the code is touched only by very few people; it may get quite messy very soon when many individuals work on the same project.
So I don't mind conventions and am willing to adhere to them as long as they don't hamper me (example the casing rules); when they do I need some convincing
IMO the verb/noun issue in class/method/property names is much more than a convention, it really helps you to better perform in OOP.
|
|
|
|
|
Fur inuff we shall agree to differ
"We can't stop here - this is bat country" - Hunter S Thompson - RIP
modified 6-Jul-20 16:32pm.
|
|
|
|
|
I am trying to understand how I would convert / combine two integers into a floating point value. For example the numbers [17352,9147] = 400.28. Could someone share an example of how this would be done? I am reading these two integer values from a Modbus device in holding registers.
modified 1-Jul-20 11:53am.
|
|
|
|
|
Hi,
one float takes 4 bytes of memory; two shorts would also take 4 bytes. One way to convert one into the other is by mapping them at the same location in memory.
I know of two ways to achieve this:
(1) the techniques available for interacting with unmanaged code and data offer the right tools. Here is how:
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Explicit)]
struct ShortsOverlayFloat {
[FieldOffset(0)]
public short I1;
[FieldOffset(2)]
public short I2;
[FieldOffset(0)]
public float F;
}
public float shortsToFloat(short i1, short i2) {
ShortsOverlayFloat S;
S.F=0;
S.I1=i1;
S.I2=i2;
return S.F;
}
and now
float f = shortsToFloat(9147, 17352);
will yield 400.2791
(2) the more orthodox way is by using some methods of the BitConverter class, such as BitConverter.ToSingle ; I haven't checked but I expect they use the above technique behind the scenes. That class uses somewhat different type names, don't let that confuse you!
PS: it is IEEE754, not 745.
modified 30-Jun-20 23:51pm.
|
|
|
|
|
Thank you. I will give this a shot today and see how I make out. Thank you for pointing out my typo, I wish I could figure out how to edit the topic title.
|
|
|
|
|
You're welcome.
BTW: You can click the "Edit" widget of your original post, and modify title and/or content.
|
|
|
|
|
Trying to work through this, I have attempted the following, but get a compile error.
int val1 = 17352;
int val2 = 9147;
byte[] buffer = { (byte)val1, (byte)val2 };
for (int n = 0; n < buffer.Length; n += 2)
{
short sample = (short)BitConverter.ToSingle(buffer, n);
MessageBox.Show(sample.ToString());
}
The exception that I currently get is: Could you help a bit more? I was able to fix the title......
Quote: System.ArgumentException
HResult=0x80070057
Message=Destination array is not long enough to copy all the items in the collection. Check array index and length.
Source=mscorlib
StackTrace:
at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
at System.BitConverter.ToSingle(Byte[] value, Int32 startIndex)
at WindowsFormsApp2.Form1.tmr_Modbus_Conn_Tick(Object sender, EventArgs e) in C:\Users\Justin Fehl\Documents\Visual Studio 2019\Projects\ModBus Development\ModbusTest2.cs:line 94
at System.Windows.Forms.Timer.OnTick(EventArgs e)
at System.Windows.Forms.Timer.TimerNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.Run(Form mainForm)
at WindowsFormsApp2.Program.Main() in C:\Users\Justin Fehl\Documents\Visual Studio 2019\Projects\ModBus Development\Modbus Development 6-26-2020\WindowsFormsApp2\Program.cs:line 19
|
|
|
|
|
The error message is pretty clear when it says
Destination array is not long enough
Apparently this line is wrong:
byte[] buffer = { (byte)val1, (byte)val2 };
How many bytes would that be?
and what is the purpose of that for loop?
It seems you didn't understand the conversion at all, why would you need ToSingle twice if you only are trying to get one Single? (Single and float are the same thing).
|
|
|
|
|
I will scratch my head awhile............
|
|
|
|
|
I was able to get this to work:
var byteval1 = BitConverter.GetBytes(val1);
var byteval2 = BitConverter.GetBytes(val2);
byte[] temp2 = new byte[4];
temp2[0] = byteval1[0];
temp2[1] = byteval1[1];
temp2[2] = byteval2[0];
temp2[3] = byteval2[1];
float myFloat = System.BitConverter.ToSingle(temp2, 0);
Console.WriteLine(myFloat);
Console.ReadLine();
|
|
|
|
|
That looks fine.
You scratched well.
|
|
|
|
|
I have product that can be connected to a PC via a USB-cable. When I connect it, I can see that the following shows up in the Device Manager under "Ports (COM & LPT)":
USB Serial Device (COM1)
Inside my C# application, I am able to query the descriptive string, "USB Serial Device". However, if I run my C# application on a computer with a French operating system, then my C# application reads "Périphérique série USB" instead. How can I obtain a list of what "USB Serial Device" is called in different languages around the world, without having to install operating systems in all possible languages on my computer?
|
|
|
|
|
|
|
Luc just pointed out what you're really looking for. Sorry, I didn't go back and look up your history.
|
|
|
|
|
Hi Dave,
I've been following arnold_w's attempts to pick the right serial port when many are available, as I am having a similar problem sometimes. IMO the SerialPort class isn't really helping out as it doesn't treat hardware characteristics of the serial ports it knows are present: Are they motherboard internal (e.g. old modem), motherboard external, USB, or whatever.
A simple question would be: when I want to use the USB-to-serial cable that is plugged into a specific USB port somewhere, what then is the one and only serial port name I should use to get at it?
The situation gets harder when multiple USB-to-serials are being used.
And then in one of my applications I have one or two serials plus several USB-based webcams, with the same question: which is which? (those cheap bastards don't have a built-in serial number, they all look absolutely identical!)
Any help is welcome.
|
|
|
|