|
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.
|
|
|
|
|
arnold_w wrote: Then it was a performance issue, I remember I couldn't use this class for a reason. SerialPort class isn't slow. When using WMI the first WMI call often is slow.
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). That is what I said.
|
|
|
|
|
When I use your code I get the following:
found: COM24 = USB Serial Device;FN=USB Serial Device (COM24); HID=USB\VID_0483&PID_5741&REV_0200&MI_00; LOC=0000.0014.0000.002.004.004.000.000.000
Who specifies HID ("USB\VID_0483&PID_5741&REV_0200&MI_00") and LOC ("0000.0014.0000.002.004.004.000.000.000")? Is something from the PC/operating system or from the USB-cable/connected product?
|
|
|
|
|
The hardwareID contains a vendorID and a productID, these obviously come from data inside the device.
The location is where the device currently sits, so obviously it does not come from inside the device.
FYI: this information can also be obtained manually following this path on Win10:
My PC/Properties/Device Manager/Ports...
|
|
|
|
|
Ok, that sounds great, then I will stop comparing the description string and compare the HID string instead. Thank you for your help!
|
|
|
|
|
hi
I have project of type Console .net framework 4.6.1. A simple hello world. No dependencies. No references.
When I right click and try publishing this project, it goes into manifest mode of publishing. Where do I change settings to make it a traditional simpler Publish, so I get a .exe file without manifest files. So that, the Publish folder can be copy pasted to any computer and it runs without issues.
Some research suggested to uncheck LINKER, but this is not available in Projects --> Properties --> Configuration
thank you
|
|
|
|
|