|
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.
|
|
|
|
|
Ah. I didn't look up the history and some description like this wasn't in the OP's question.
My bad.
|
|
|
|
|
Hi,
1.
SerialPort does enumerate all serial ports as it should, there is no problem, except it does not know about the underlying hardware, so for instance you can't differentiate motherboard vs USB ports.
2.
I trust USB is called USB in all languages that use latin character sets, so you could use String.Contains on the friendlyname.
3.
I have my own class enumerating all serial ports, its underlying technology is somewhat similar to the class you referred; here it is, use at your leisure. It returns a list of struct ComInfo which holds the friendlyname as well as the location (for USB serial ports this is a concatenation of port numbers, pointing to the USB port actually used).
BTW: you will have to remove all lines that hold an "env", these are used for my debugging only.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
namespace LP_Core {
public class LP_SerialPortInfo {
private static List<ComInfo> serialPorts=new List<ComInfo>();
private const UInt32 DIGCF_PRESENT=0x00000002;
private const UInt32 DIGCF_DEVICEINTERFACE=0x00000010;
private const UInt32 DICS_FLAG_GLOBAL=0x00000001;
private const UInt32 DIREG_DEV=0x00000001;
private const UInt32 KEY_QUERY_VALUE=0x0001;
private const string GUID_DEVINTERFACE_COMPORT="86E0D1E0-8089-11D0-9CE4-08003E301F73";
[StructLayout(LayoutKind.Sequential)]
private struct SP_DEVINFO_DATA {
public Int32 cbSize;
public Guid ClassGuid;
public Int32 DevInst;
public UIntPtr Reserved;
};
public enum ComPortCharacteristic {
Description=0x00000000,
FriendlyName=0x0000000C,
HardwareID=0x00000001,
Location=0x0000000D
}
[DllImport("setupapi.dll")]
private static extern Int32 SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);
[DllImport("setupapi.dll")]
private static extern bool SetupDiEnumDeviceInfo(IntPtr DeviceInfoSet, Int32 MemberIndex, ref SP_DEVINFO_DATA DeviceInterfaceData);
[DllImport("setupapi.dll", CharSet=CharSet.Auto, SetLastError=true)]
private static extern bool SetupDiGetDeviceRegistryProperty(IntPtr deviceInfoSet, ref SP_DEVINFO_DATA deviceInfoData,
uint property, out UInt32 propertyRegDataType, StringBuilder propertyBuffer, uint propertyBufferSize, out UInt32 requiredSize);
[DllImport("setupapi.dll", SetLastError=true)]
private static extern IntPtr SetupDiGetClassDevs(ref Guid gClass, UInt32 iEnumerator, IntPtr hParent, UInt32 nFlags);
[DllImport("Setupapi", CharSet=CharSet.Auto, SetLastError=true)]
private static extern IntPtr SetupDiOpenDevRegKey(IntPtr hDeviceInfoSet, ref SP_DEVINFO_DATA deviceInfoData, uint scope,
uint hwProfile, uint parameterRegistryValueKind, uint samDesired);
[DllImport("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegQueryValueExW", SetLastError=true)]
private static extern int RegQueryValueEx(IntPtr hKey, string lpValueName, int lpReserved, out uint lpType,
StringBuilder lpData, ref uint lpcbData);
[DllImport("advapi32.dll", CharSet=CharSet.Ansi, SetLastError=true, ExactSpelling=true)]
private static extern int RegCloseKey(IntPtr hKey);
[DllImport("kernel32.dll")]
private static extern Int32 GetLastError();
public struct ComInfo {
public string Name;
public string Description;
public string FriendlyName;
public string Location;
public string HardwareID;
}
public static List<ComInfo> GetComPorts() {
serialPorts.Clear();
Guid guidComPorts=new Guid(GUID_DEVINTERFACE_COMPORT);
IntPtr hDeviceInfoSet=SetupDiGetClassDevs(
ref guidComPorts, 0, IntPtr.Zero, DIGCF_PRESENT|DIGCF_DEVICEINTERFACE);
if (hDeviceInfoSet!=IntPtr.Zero) {
try {
List<ComInfo> devices=new List<ComInfo>();
Int32 iMemberIndex=0;
while (true) {
SP_DEVINFO_DATA deviceInfoData=new SP_DEVINFO_DATA();
deviceInfoData.cbSize=Marshal.SizeOf(typeof(SP_DEVINFO_DATA));
bool success=SetupDiEnumDeviceInfo(hDeviceInfoSet, iMemberIndex, ref deviceInfoData);
if (!success) {
break;
}
ComInfo comInfo=new ComInfo();
comInfo.Name=getName(hDeviceInfoSet, deviceInfoData);
comInfo.Description=getString((UInt32)ComPortCharacteristic.Description, hDeviceInfoSet, deviceInfoData);
comInfo.FriendlyName=getString((UInt32)ComPortCharacteristic.FriendlyName, hDeviceInfoSet, deviceInfoData);
comInfo.HardwareID=getString((UInt32)ComPortCharacteristic.HardwareID, hDeviceInfoSet, deviceInfoData);
comInfo.Location=getString((UInt32)ComPortCharacteristic.Location, hDeviceInfoSet, deviceInfoData);
env.log("found: "+comInfo.Name+" = "+comInfo.Description+";FN="+comInfo.FriendlyName+
"; HID="+comInfo.HardwareID+"; LOC="+comInfo.Location);
serialPorts.Add(comInfo);
iMemberIndex++;
}
} finally {
SetupDiDestroyDeviceInfoList(hDeviceInfoSet);
}
}
return serialPorts;
}
private static string getName(IntPtr pDevInfoSet, SP_DEVINFO_DATA deviceInfoData) {
string name = "???";
IntPtr hDeviceRegistryKey = SetupDiOpenDevRegKey(pDevInfoSet, ref deviceInfoData,
DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_QUERY_VALUE);
if (hDeviceRegistryKey!=IntPtr.Zero) {
StringBuilder buf = new StringBuilder(256);
try {
uint lpRegKeyType;
uint length = (uint)buf.Capacity;
int result = RegQueryValueEx(hDeviceRegistryKey, "PortName", 0, out lpRegKeyType, buf, ref length);
if (result!=0) {
throw new Exception("Can not read registry value PortName for device "+deviceInfoData.ClassGuid);
}
} finally {
RegCloseKey(hDeviceRegistryKey);
}
name=buf.ToString();
}
return name;
}
private static string getString(UInt32 SPDRP, IntPtr hDeviceInfoSet, SP_DEVINFO_DATA deviceInfoData) {
StringBuilder buf = new StringBuilder(256);
uint propRegDataType;
uint length = (uint)buf.Capacity;
bool success = SetupDiGetDeviceRegistryProperty(hDeviceInfoSet, ref deviceInfoData, SPDRP,
out propRegDataType, buf, length, out length);
if (!success) {
throw new Exception("Can not read registry value PortName for device "+deviceInfoData.ClassGuid);
}
return buf.ToString();
}
}
}
|
|
|
|
|
Luc Pattyn wrote: 1.
SerialPort does enumerate all serial ports as it should, there is no problem, except it does not know about the underlying hardware, so for instance you can't differentiate motherboard vs USB ports. Then it was a performance issue, I remember I couldn't use this class for a reason. I also refresh the list on the fly each time a USB-cable is connected/disconnected so if I remember correctly it was annoying.
Luc Pattyn wrote: 2.
I trust USB is called USB in all languages that use latin character sets, so you could use String.Contains on the friendlyname. In my case it's not enough to find anything that contains "USB", I also need to categorize the cable/device and distinguish "USB Serial Device" from "USB Serial Port", my C# application supports products that show up as either one of these.
Luc Pattyn wrote: I have my own class enumerating all serial ports, its underlying technology is somewhat similar to the class you referred; here it is, use at your leisure. It returns a list of struct ComInfo which holds the friendlyname as well as the location (for USB serial ports this is a concatenation of port numbers, pointing to the USB port actually used). It has a lot of similarities with the code I'm already using (I slightly modified this code: Svetlin Nakov – Official Web Site and Blog » Enumerate All COM Ports and Find Their Name and Description in C#[^] ). Are you saying your code will always return a description string in English, even if the operating system is non-English or what would I gain be switching to your code?
|
|
|
|
|
Could you pick the information you need from WMI?
When I open \\.\ROOT\CIMV2 class Win32_SerialPort, I see one COM1 instance, which has 47 properties (some of which are empty values), like Availability, Caption, Name, ... ProviderType, ...
Maybe you have what you need (and then some!) there. As I do not have a USB COM-port at the moment, I can't tell what it wouold look like in WMI, but try it out!
If you haven't got a WMI browser: One that is reasonable OK to use is CodePlex Archive WMI Explorer[^]. Once you have found what you are looking for, setting up a search in C# code is fairly simple. Like this I use to get information on disk partitions:
ManagementScope scope = new ManagementScope(@"\\.\root\microsoft\windows\storage");
using (ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT * FROM MSFT_Partition")) {
scope.Connect();
searcher.Scope = scope;
foreach (ManagementObject queryObj in searcher.Get()) {
char driveletter = (char)queryObj["DriveLetter"];
if (driveletter == '\0') continue;
FsVolume part = new FsVolume();
part.driveletter = driveletter;
part.diskNumber = (uint) queryObj["DiskNumber"];
part.partNo = (uint) queryObj["PartitionNumber"];
part.partGuid = (string)queryObj["UniqueID"];
partList.Add(part);
}
}
Your code will obviously address other classes and properties - you'll find them using WMI Explorer (or some other WMI browser).
This won't give you "USB Serial Device" in all the languages of the world, but I assume that if you find the information you need by inspecting properties from WMI, it doesn't really matter what a rose is called in the current language.
|
|
|
|
|
In my old ( >10 years ago) code I was using this:
public StatusType getAvailableComPorts()
{
StatusType status = new StatusType(StatusCodeType.OK, null, "", "");
try
{
string[] availableComPorts = SerialPort.GetPortNames();
if ((availableComPorts == null) || (availableComPorts.Length <= 0))
{
status.statusCode = StatusCodeType.OTHER_ERROR;
status.errorHeading = "No Com Ports Available";
status.errorMessage = "No available com port was found on your computer. Please add a com port or use a different computer to control your PTT box.";
return status;
}
Array.Sort(availableComPorts, new ComPortComparerClass());
ComPortInformation[] comPortsInformation = new ComPortInformation[availableComPorts.Length];
for (int i = 0; i < comPortsInformation.Length; i++)
{
comPortsInformation[i] = new ComPortInformation(availableComPorts[i]);
}
if ((availableComPorts != null) && (0 < availableComPorts.Length))
{
try
{
ManagementObjectSearcher searcher = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_PnPEntity");
for (int i = availableComPorts.Length - 1; 0 <= i; i--)
{
foreach (ManagementObject queryObj in searcher.Get())
{
if ((queryObj["Name"] != null) && (queryObj["Name"].ToString().Contains("(" + availableComPorts[i] + ")")))
{
comPortsInformation[i].comPortDescriptor = queryObj["Caption"].ToString().Replace("(" + availableComPorts[i] + ")", "").Trim();
break;
}
}
}
}
catch (ManagementException)
{
}
}
status.value = comPortsInformation;
}
catch (Exception e)
{
status.statusCode = StatusCodeType.OTHER_ERROR;
status.errorHeading = "Error";
status.errorMessage = "Error getting available com ports. Technical info: " + e.Message;
}
return status;
}
For some reason I stepped away from this (maybe it was this part that was too slow?) and started using the other approach instead.
|
|
|
|
|