How to know if a specific device removed C# - c#

I am developing a program that uses a hardware (I am using Arduino) to collect data and then store the data in the database. As there is no way to get assured that the serialport is receiving the data, I am trying to implement some code snippet to declare hardware removal and then check if there is my desired device still connected or otherwise inform the user.
What I have come to up to now is:
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
switch (m.Msg)
{
case WM_DEVICECHANGE:
switch ((int)m.WParam)
{
case DBT_DEVICEARRIVAL:
listBox1.Items.Add("New Device Arrived");
int devType = Marshal.ReadInt32(m.LParam, 4);
if (devType == DBT_DEVTYP_PORT)
{
string portName = Marshal.PtrToStringAuto((IntPtr)((long)m.LParam + 12));
listBox1.Items.Add("the name of the port is " + portName);
}
break;
case DBT_DEVICEREMOVECOMPLETE:
{
listBox1.Items.Add("Device Removed");
}
break;
}
break;
}
}
which informs me if any changes in device connection to the PC has happened. Also the following code tells me about existence of the arduino in the list of connected devices:
ManagementScope connectionScope = new ManagementScope();
SelectQuery serialQuery = new SelectQuery("SELECT * FROM Win32_SerialPort");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(connectionScope, serialQuery);
try
{
foreach (ManagementObject item in searcher.Get())
{
string desc = item["Description"].ToString();
string deviceId = item["DeviceID"].ToString();
if (!desc.Contains("Arduino"))
{
// Inform the user about the disconnection of the arduino
}
}
}
catch //(ManagementException e)
{
/* Do Nothing */
}
How ever, when I mix the codes to inform me when arduino is unplugged, they do not comply with each other and throw the error of:
Transition into COM context 0x8ff990 for this RuntimeCallableWrapper failed with the following error: An outgoing call cannot be made since the application is dispatching an input-synchronous call. (Exception from HRESULT: 0x8001010D (RPC_E_CANTCALLOUT_ININPUTSYNCCALL)). This is typically because the COM context 0x8ff990 where this RuntimeCallableWrapper was created has been disconnected or it is busy doing something else and cannot process the context transition. No proxy will be used to service the request on the COM component and calls will be made to the COM component directly. This may cause corruption or data loss. To avoid this problem, please ensure that all COM contexts/apartments/threads stay alive and are available for context transition, until the application is completely done with the RuntimeCallableWrappers that represents COM components that live inside them.
Any Help is appreciated, Thanks in advance.
PS: For your further information, the whole coded is:
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
switch (m.Msg)
{
case WM_DEVICECHANGE:
switch ((int)m.WParam)
{
case DBT_DEVICEARRIVAL:
listBox1.Items.Add("New Device Arrived");
int devType = Marshal.ReadInt32(m.LParam, 4);
if (devType == DBT_DEVTYP_PORT)
{
string portName = Marshal.PtrToStringAuto((IntPtr)((long)m.LParam + 12));
listBox1.Items.Add("the name of the port is " + portName);
}
break;
case DBT_DEVICEREMOVECOMPLETE:
{
listBox1.Items.Add("Device Removed");
ManagementScope connectionScope = new ManagementScope();
SelectQuery serialQuery = new SelectQuery("SELECT * FROM Win32_SerialPort");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(connectionScope, serialQuery);
try
{
foreach (ManagementObject item in searcher.Get())
{
string desc = item["Description"].ToString();
string deviceId = item["DeviceID"].ToString();
if (desc.Contains("Arduino"))
{
MessageBox.Show(deviceId);
}
}
}
catch //(ManagementException e)
{
/* Do Nothing */
}
}
break;
}
break;
}
}

public partial class Form1 : Form
{
ManagementEventWatcher watcher;
// Not all arduinos have 'Arduino' in their serialport driver 'Description', So check for a Com port Number.
private static string MustHavePort = "COM3"; //Port to check for.
public Form1()
{
InitializeComponent();
watcher = new ManagementEventWatcher("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 2 or EventType = 3");
watcher.EventArrived += new EventArrivedEventHandler(USBChangedEvent);
watcher.Start();
}
public void USBChangedEvent(object sender, EventArrivedEventArgs e)
{
(sender as ManagementEventWatcher).Stop();
this.Invoke((MethodInvoker)delegate
{
ManagementObjectSearcher deviceList = new ManagementObjectSearcher("Select Name, Description, DeviceID from Win32_SerialPort");
// List to store available USB serial devices plugged in. (Arduino(s)/Cell phone(s)).
List<String> ComPortList = new List <String>();
listBox1.Items.Clear();
textBox1.Text = "";
// Any results? There should be!
if (deviceList != null)
{
// Enumerate the devices
foreach (ManagementObject device in deviceList.Get())
{
string SerialPortNumber = device["DeviceID"].ToString();
string serialName = device["Name"].ToString();
string SerialDescription = device["Description"].ToString();
ComPortList.Add(SerialPortNumber);
listBox1.Items.Add(SerialPortNumber);
listBox1.Items.Add(serialName);
listBox1.Items.Add(SerialDescription);
}
}
else
{
listBox1.Items.Add("NO SerialPorts AVAILABLE!");
// Inform the user about the disconnection of the arduino ON PORT 3 etc...
SendAlarm();
}
if (!ComPortList.Contains(MustHavePort))
{
// Inform the user about the disconnection of the arduino ON PORT 3 etc...
SendAlarm();
}
});
(sender as ManagementEventWatcher).Start();
}
private void SendAlarm()
{
textBox1.Text = "Alarm..." + MustHavePort + "Port Missing...";
MessageBox.Show("Error " + MustHavePort + " Port Missing!!!", "Serial Port Error.");
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
watcher.Stop();
}
}

Related

Detect USB Eject event with WMI

I use code like here Detecting USB drive insertion and removal using windows service and c# to detect when a USB device is plugged in / removed.
But when the user "ejects" the device leaving it plugged in I get no notification at all.
Of course when I try to access such a drive it will fail.
Is there a different WMI Event I can use to get a notification about this situation?
The link #Jimi posted brings me a (BIG) step further.
public void AddWMIWatcher() {
WqlEventQuery query = new WqlEventQuery();
ManagementScope scope = new ManagementScope("root\\CIMV2");
query.EventClassName = "__InstanceOperationEvent";
query.WithinInterval = new TimeSpan(0, 0, 3);
query.Condition = #"TargetInstance ISA 'Win32_DiskDrive' ";
ManagementEventWatcher watcher = new ManagementEventWatcher(scope, query);
watcher.EventArrived += Watcher_EventArrived;
watcher.Query = query;
watcher.Start();
}
private void Watcher_EventArrived(object sender, EventArrivedEventArgs e) {
ManagementBaseObject baseObject = e.NewEvent;
if(baseObject.ClassPath.ClassName.Equals("__InstanceCreationEvent")) {
Console.WriteLine("A drive was connected");
}
else if(baseObject.ClassPath.ClassName.Equals("__InstanceDeletionEvent")) {
Console.WriteLine("A drive was removed");
}
else if(baseObject.ClassPath.ClassName.Equals("__InstanceModificationEvent")) {
Console.WriteLine("A drive was changed");
//that is what I'm looking for
}
I get an __InstanceModificationEvent - the only thing I have to find out is how to get the affected drive (letter) out of this event.

Sending SMS from Windows Forms Application

I want to send SMS from my Application that is under developing on Windows Forms Application, The trouble to achieving this task is getting Port Number, I'm suffering by this issue over a week, kindly guide me, I'm sending uploading my code, one thing more is that I'm using Android OS on my cell phone where I have installed SIM card, and device vendor is HUAWEI. thanks a lot in advance......
private void button1_Click(object sender, EventArgs e)
{
GsmCommMain comm;
int portn = 0;
System.IO.Ports.SerialPort _srp = new System.IO.Ports.SerialPort();
try
{
Cursor.Current = Cursors.Cross;
System.Management.ManagementObjectSearcher searcher = new System.Management.ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_SerialPort");
Cursor = Cursors.WaitCursor;
foreach (System.Management.ManagementObject queryObj in searcher.Get())
{
Cursor = Cursors.Hand;
StatusLabel.Text = "Just Enterd in foreach Block...";
if (queryObj != null)
{
Cursor = Cursors.UpArrow;
object captionObj = queryObj["DESCRIPTION"];//Model Name
object capdeviceid = queryObj["DEVICEID"];//Comm port
object MaxBaudRate = queryObj["MAXBAUDRATE"];
object connstatus = queryObj["STATUS"];
object ct = queryObj.ToString();
int timeoutsec = 300;
StatusLabel.Text = "USB Modem Condition Main Enter Na Ho Saki...";
if (captionObj.ToString().Contains("USB Modem"))
{
_srp.PortName = capdeviceid.ToString();
portn = Convert.ToInt32(capdeviceid.ToString().Substring(3));
label1.Text = _srp.PortName + " ,,, " + portn;
comm = new GsmComm.GsmCommunication.GsmCommMain(portn, Convert.ToInt32(MaxBaudRate), timeoutsec);
StatusLabel.Text = "USB Modem Condition Main Entry...";
comm.Open();
if (comm.IsOpen())
btnSMSConnect.BackColor = Color.Green;
MessageBox.Show("Not Opening..." );
StatusLabel.Text = "Not Opening...";
}
}
}
}
catch (Exception e15)
{
MessageBox.Show("An error occurred while querying for WMI data: " + e15.Message);
btnSMSConnect.BackColor = Color.Transparent;
//comm.Close();
_srp.Close();
}
}
}
}

Why value does not fall within the expected range when setting Value Changed for Gatt Characteristic

I would like to keep on reading characteristic/set value changed event handlers for characteristics from my BLE 4.0 device, by using the ValueChanged callback in Universal Windows Platform C# in Visual Studio 2017.
I followed some tutorial from these sites: Damian Blog's Windows Universal with BLE, Bluetooth Gatt's Git Hub, Bluetooth Generic Attribute Profile - Heart Rate Service and Dr. Jukka's mobile Blog on BLE. All of them are using ValueChanged and I have tried to follow what they did.
Unfortunately, instead of ValueChanged being triggered, I receive the following error when using the ValueChanged callback.
System.ArgumentException: 'Value does not fall within the expected range.'
This line of code is producing the error:
characteristic.ValueChanged += Oncharacteristic_ValueChanged;
Here is more details of my source code:
NOTE: I am using COM 7 for my dongler and my program could discover the BLE's device name, and could discover the Uuid of the services and characteristics.
public List<string> serviceList = new List<string>();
public List<string> characteristicList = new List<string>();
public BluetoothLEDevice myDevice { get; set; }
public MainPage()
{
this.InitializeComponent();
}
private async void Page_Loaded(object sender, RoutedEventArgs e)
{
// Find the com port
string selector = SerialDevice.GetDeviceSelector("COM7");
DeviceInformationCollection devices = await DeviceInformation.FindAllAsync(selector);
if (devices.Count > 0)
{
var dialog = new MessageDialog("Com Device found");
await dialog.ShowAsync();
DeviceInformation deviceInfo = devices[0];
SerialDevice serialDevice = await SerialDevice.FromIdAsync(deviceInfo.Id);
serialDevice.BaudRate = 9600;
serialDevice.DataBits = 8;
serialDevice.StopBits = SerialStopBitCount.One;
serialDevice.Parity = SerialParity.None;
}
else
{
MessageDialog popup = new MessageDialog("Sorry, no device found.");
await popup.ShowAsync();
}
// After com port is found, search for device
foreach (DeviceInformation di in await DeviceInformation.FindAllAsync(BluetoothLEDevice.GetDeviceSelector()))
{
BluetoothLEDevice bleDevice = await BluetoothLEDevice.FromIdAsync(di.Id);
// Display BLE device name
var dialogBleDeviceName = new MessageDialog("BLE Device Name " + bleDevice.Name);
await dialogBleDeviceName.ShowAsync();
myDevice = bleDevice;
}
// Check device connection
myDevice.ConnectionStatusChanged += OnConnectionStatusChanged;
foreach (var service in myDevice.GattServices)
{
serviceList.Add(service.Uuid.ToString());
// Verify if service is discovered by displaying a popup
MessageDialog serviceUuidPopUp = new MessageDialog("Adding Service Uuid to list " + service.Uuid.ToString() );
await serviceUuidPopUp.ShowAsync();
foreach (var characteristic in service.GetAllCharacteristics())
{
var characteristicUuid = characteristic.Uuid.ToString().ToLowerInvariant();
characteristicList.Add(characteristicUuid);
// Verify if characteristic is discovered by displaying a popup
MessageDialog charUuidPopUp = new MessageDialog("Adding characteristic Uuid to list " + characteristicUuid);
await charUuidPopUp.ShowAsync();
// set value changed event handlers for characteristics
characteristic.ValueChanged += Oncharacteristic_ValueChanged;
}
}
}
private void OnConnectionStatusChanged(BluetoothLEDevice sender, object args)
{
if (sender.ConnectionStatus == BluetoothConnectionStatus.Connected)
{
System.Diagnostics.Debug.WriteLine("Connected");
}
else
{
System.Diagnostics.Debug.WriteLine("Disconnected");
}
}
private void Oncharacteristic_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args)
{
byte[] data = new byte[args.CharacteristicValue.Length];
DataReader.FromBuffer(
args.CharacteristicValue).ReadBytes(data);
string text = Encoding.UTF8.GetString(data, 0, data.Length);
}
UPDATE 1
I tried to check Characteristic Properties before set value changed event handlers for my characteristics by following the answer given by rudi belt on SO.
if (characteristic.CharacteristicProperties == (GattCharacteristicProperties.Read | GattCharacteristicProperties.Notify))
{
characteristic.ValueChanged += Oncharacteristic_ValueChanged;
}
Unfortunately, this IF statement is not executed.
UPDATE 2
I have tried to remove ALL the codes inside Oncharacteristic_ValueChanged method. But it still gives me the same error
System.ArgumentException: 'Value does not fall within the expected range.'
I have been spending a lot of time trying to solve this problem. I will be very happy if anyone can help me on this. Thank you!
Reading your efforts in the former question I can provide a working example, but first some explanation.
myDevice.ConnectionStatusChanged is not needed, it is only used to notice a connection is lost or connected. You have to connect to your device first and handle things in the connection method.
After you have succeeded in connecting you have to get the service that contains the characteristic you want to use for read, write, notify or indicate.
When you have selected the service You can get the characteristics of that service.
Select the characteristic by Uuid, or in my example with CharacteristicProperties.HasFlag.
This flag in my example is Notify.
In the code comments you find extra info.
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.GenericAttributeProfile;
using Windows.Devices.Enumeration;
using Windows.UI.Popups;
using Windows.UI.Xaml.Controls;
namespace App1
{
public sealed partial class MainPage : Page
{
GattDeviceServicesResult serviceResult = null;
private BluetoothLEDevice myDevice;
private GattCharacteristic selectedCharacteristic;
public MainPage()
{
this.InitializeComponent();
ConnectDevice();
}
private async void ConnectDevice()
{
//This works only if your device is already paired!
foreach (DeviceInformation di in await DeviceInformation.FindAllAsync(BluetoothLEDevice.GetDeviceSelector()))
{
BluetoothLEDevice bleDevice = await BluetoothLEDevice.FromIdAsync(di.Id);
// Display BLE device name
var dialogBleDeviceName = new MessageDialog("BLE Device Name " + bleDevice.Name);
await dialogBleDeviceName.ShowAsync();
myDevice = bleDevice;
}
if (myDevice != null)
{
int servicesCount = 3;//Fill in the amount of services from your device!!!!!
int tryCount = 0;
bool connected = false;
while (!connected)//This is to make sure all services are found.
{
tryCount++;
serviceResult = await myDevice.GetGattServicesAsync();
if (serviceResult.Status == GattCommunicationStatus.Success && serviceResult.Services.Count >= servicesCount)
{
connected = true;
Debug.WriteLine("Connected in " + tryCount + " tries");
}
if (tryCount > 5)//make this larger if faild
{
Debug.WriteLine("Failed to connect to device ");
return;
}
}
if (connected)
{
for (int i = 0; i < serviceResult.Services.Count; i++)
{
var service = serviceResult.Services[i];
//This must be the service that contains the Gatt-Characteristic you want to read from or write to !!!!!!!.
string myServiceUuid = "0000ffe0-0000-1000-8000-00805f9b34fb";
if (service.Uuid.ToString() == myServiceUuid)
{
Get_Characteriisics(service);
break;
}
}
}
}
}
private async void Get_Characteriisics(GattDeviceService myService)
{
var CharResult = await myService.GetCharacteristicsAsync();
if (CharResult.Status == GattCommunicationStatus.Success)
{
foreach (GattCharacteristic c in CharResult.Characteristics)
{
if (c.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Notify))
{
selectedCharacteristic = c;
break;
}
}
try
{
// Write the ClientCharacteristicConfigurationDescriptor in order for server to send notifications.
var result = await selectedCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(
GattClientCharacteristicConfigurationDescriptorValue.Notify);
if (result == GattCommunicationStatus.Success)
{
var dialogNotifications = new MessageDialog("Successfully registered for notifications");
await dialogNotifications.ShowAsync();
selectedCharacteristic.ValueChanged += SelectedCharacteristic_ValueChanged;
}
else
{
var dialogNotifications = new MessageDialog($"Error registering for notifications: {result}");
await dialogNotifications.ShowAsync();
}
}
catch (Exception ex)
{
// This usually happens when not all characteristics are found
// or selected characteristic has no Notify.
var dialogNotifications = new MessageDialog(ex.Message);
await dialogNotifications.ShowAsync();
await Task.Delay(100);
Get_Characteriisics(myService); //try again
//!!! Add a max try counter to prevent infinite loop!!!!!!!
}
}
else
{
var dialogNotifications = new MessageDialog("Restricted service. Can't read characteristics");
await dialogNotifications.ShowAsync();
}
}
private void SelectedCharacteristic_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args)
{
}
}
}
If you have problems with this code feel free to ask in comments.

SMS connecting to a phone from C# and getting a response

I wrote code to send an SMS using my GSM phone which is attached to the computer through COM port. The code is below.
The problem is I do see that it is in the outbox of the phone and it actually appears to have been sent, but when I contact the recipient they say that I have not received the message.
I test the phone, and I create and send a message using only the phone and it works perfectly. However, when I do this with my code, it APPEARS to have been sent, and I am getting all the correct AT COMMAND responses from the phone, but the message is actually NOT sent.
Here is the code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.IO.Ports;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
SerialPort serialPort1;
int m_iTxtMsgState = 0;
const int NUM_MESSAGE_STATES = 4;
const string RESERVED_COM_1 = "COM1";
const string RESERVED_COM_4 = "COM4";
public Form1()
{
InitializeComponent();
this.Closing += new CancelEventHandler(Form1_Closing);
}
private void Form1_Load(object sender, EventArgs e)
{
serialPort1 = new SerialPort(GetUSBComPort());
if (serialPort1.IsOpen)
{
serialPort1.Close();
}
serialPort1.Open();
//ThreadStart myThreadDelegate = new ThreadStart(ReceiveAndOutput);
//Thread myThread = new Thread(myThreadDelegate);
//myThread.Start();
this.serialPort1.DataReceived += new SerialDataReceivedEventHandler(sp_DataReceived);
}
private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
serialPort1.Close();
}
private void SendLine(string sLine)
{
serialPort1.Write(sLine);
sLine = sLine.Replace("\u001A", "");
consoleOut.Text += sLine;
}
public void DoWork()
{
ProcessMessageState();
}
public void ProcessMessageState()
{
switch (m_iTxtMsgState)
{
case 0:
m_iTxtMsgState = 1;
SendLine("AT\r\n"); //NOTE: SendLine must be the last thing called in all of these!
break;
case 1:
m_iTxtMsgState = 2;
SendLine("AT+CMGF=1\r\n");
break;
case 2:
m_iTxtMsgState = 3;
SendLine("AT+CMGW=" + Convert.ToChar(34) + "+9737387467" + Convert.ToChar(34) + "\r\n");
break;
case 3:
m_iTxtMsgState = 4;
SendLine("A simple demo of SMS text messaging." + Convert.ToChar(26));
break;
case 4:
m_iTxtMsgState = 5;
break;
case 5:
m_iTxtMsgState = NUM_MESSAGE_STATES;
break;
}
}
private string GetStoredSMSID()
{
return null;
}
/* //I don't think this part does anything
private void serialPort1_DataReceived_1(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
string response = serialPort1.ReadLine();
this.BeginInvoke(new MethodInvoker(() => textBox1.AppendText(response + "\r\n")));
}
*/
void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
Thread.Sleep(500);
char[] msg;
msg = new char[613];
int iNumToRead = serialPort1.BytesToRead;
serialPort1.Read(msg, 0, iNumToRead);
string response = new string(msg);
this.BeginInvoke(new MethodInvoker(() => textBox1.AppendText(response + "\r\n")));
serialPort1.DiscardInBuffer();
if (m_iTxtMsgState == 4)
{
int pos_cmgw = response.IndexOf("+CMGW:");
string cmgw_num = response.Substring(pos_cmgw + 7, 4);
SendLine("AT+CMSS=" + cmgw_num + "\r\n");
//stop listening to messages received
}
if (m_iTxtMsgState < NUM_MESSAGE_STATES)
{
ProcessMessageState();
}
}
catch
{ }
}
private void button1_Click(object sender, EventArgs e)
{
m_iTxtMsgState = 0;
DoWork();
}
private void button2_Click(object sender, EventArgs e)
{
string[] sPorts = SerialPort.GetPortNames();
foreach (string port in sPorts)
{
consoleOut.Text += port + "\r\n";
}
}
private string GetUSBComPort()
{
string[] sPorts = SerialPort.GetPortNames();
foreach (string port in sPorts)
{
if (port != RESERVED_COM_1
&& port != RESERVED_COM_4)
{
return port;
}
}
return null;
}
}
This is my code that works (tested) with U9 Telia cellular modem:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Configuration;
using UsbEject.Library;
using Utils;
namespace Hardware
{
public class TeliaModem : IDisposable
{
public delegate void NewSmsHandler(InboundSMS sms);
public event NewSmsHandler OnNewSMS;
#region private data
System.IO.Ports.SerialPort modemPort;
Timeouter _lastModemKeepAlive;
private delegate void DataReceivedDelegate();
private DataReceivedDelegate dataReceivedDelegate;
Queue<OutboundSMS> _outSmses = new Queue<OutboundSMS>();
enum ModemState
{
Error = -1, NotInitialized, PowerUp, Initializing, Idle,
SendingSMS, KeepAliveAwaitingResponse
};
Timeouter ModemStateTimeouter = new Timeouter(timeout_s: 10, autoReset: false);
ModemState _modemState = ModemState.NotInitialized;
ModemState CurrentModemState
{
get
{
return _modemState;
}
set
{
_modemState = value;
ModemStateTimeouter.Reset();
NihLog.Write(NihLog.Level.Debug, "State changed to: " + value.ToString());
}
}
private System.Windows.Forms.Control _mainThreadOwnder;
#endregion
public TeliaModem(System.Windows.Forms.Control mainThreadOwnder)
{
_mainThreadOwnder = mainThreadOwnder;
dataReceivedDelegate = new DataReceivedDelegate(OnDataReceived);
}
public void SendSMS(string phone, string text)
{
_outSmses.Enqueue(new OutboundSMS(phone, text));
HeartBeat();
}
private void SendSmsNow()
{
OutboundSMS sms = _outSmses.Peek();
sms.Attempt++;
if (sms.Attempt > sms.MaxTries)
{
NihLog.Write(NihLog.Level.Error, "Failure to send after " + sms.MaxTries + " tries");
_outSmses.Dequeue();
return;
}
NihLog.Write(NihLog.Level.Info, "Sending SMS: " + sms.ToString());
WriteToModem("AT+CMGS=\"" + sms.Destination + "\"\r");
System.Threading.Thread.Sleep(500);
WriteToModem(sms.Text);
byte[] buffer = new byte[1];
buffer[0] = 26; // ^Z
modemPort.Write(buffer, offset:0, count:1);
CurrentModemState = ModemState.SendingSMS;
}
public void Dispose()
{
UninitModem();
}
public void HeartBeat()
{
if (CurrentModemState == ModemState.NotInitialized)
{
TryInitModem();
return;
}
if (IsTransitionalState(CurrentModemState) && ModemStateTimeouter.IsTimedOut())
{
NihLog.Write(NihLog.Level.Error, "Modem error. Timed out during " + CurrentModemState);
CurrentModemState = ModemState.Error;
return;
}
if (CurrentModemState == ModemState.Idle && _lastModemKeepAlive.IsTimedOut())
{
// Send keepalive
WriteToModem("AT\r");
CurrentModemState = ModemState.KeepAliveAwaitingResponse;
return;
}
if (CurrentModemState == ModemState.Error)
{
NihLog.Write(NihLog.Level.Debug, "Reenumerating modem...");
UninitModem();
return;
}
if (_outSmses.Count != 0 && CurrentModemState == ModemState.Idle)
{
SendSmsNow();
return;
}
}
private string pendingData;
private void OnDataReceived()
{
// Called in the main thread
string nowWhat = modemPort.ReadExisting();
pendingData += nowWhat;
string[] lines = pendingData.Split(new string[] { "\r\n" }, StringSplitOptions.None);
if (lines.Length == 0)
{
pendingData = string.Empty;
return;
}
else
{
pendingData = lines[lines.Length - 1];
}
// This happens in main thread.
for (int i = 0; i < lines.Length - 1; i++)
{
string line = lines[i];
if (line.Length >= 5 && line.Substring(0, 5) == "+CMT:")
{
// s+= read one more line
if (i == lines.Length - 1) // no next line
{
pendingData = line + "\r\n" + pendingData; // unread the line
continue;
}
string line2 = lines[++i];
NihLog.Write(NihLog.Level.Debug, "RX " + line);
NihLog.Write(NihLog.Level.Debug, "RX " + line2);
InboundSMS sms = new InboundSMS();
sms.ParseCMT(line, line2);
if(OnNewSMS != null)
OnNewSMS(sms);
}
else // Not a composite
NihLog.Write(NihLog.Level.Debug, "RX " + line);
if (line == "OK")
{
OnModemResponse(true);
}
else if (line == "ERROR")
{
OnModemResponse(false);
NihLog.Write(NihLog.Level.Error, "Modem error");
}
}
}
private void ProcessSmsResult(bool ok)
{
if (!ok)
{
OutboundSMS sms = _outSmses.Peek();
if (sms.Attempt < sms.MaxTries)
{
NihLog.Write(NihLog.Level.Info, "Retrying sms...");
return;
}
NihLog.Write(NihLog.Level.Error, "Failed to send SMS: " + sms.ToString());
}
_outSmses.Dequeue();
}
private void OnModemResponse(bool ok)
{
if (CurrentModemState == ModemState.SendingSMS)
ProcessSmsResult(ok);
if (!ok)
{
NihLog.Write(NihLog.Level.Error, "Error during state " + CurrentModemState.ToString());
CurrentModemState = ModemState.Error;
return;
}
switch (CurrentModemState)
{
case ModemState.NotInitialized:
return;
case ModemState.PowerUp:
WriteToModem("ATE0;+CMGF=1;+CSCS=\"IRA\";+CNMI=1,2\r");
CurrentModemState = ModemState.Initializing;
break;
case ModemState.Initializing:
case ModemState.SendingSMS:
case ModemState.KeepAliveAwaitingResponse:
CurrentModemState = ModemState.Idle;
break;
}
}
private void CloseU9TelitNativeApp()
{
bool doneSomething;
do
{
doneSomething = false;
Process[] processes = Process.GetProcessesByName("wirelesscard");
foreach (Process p in processes)
{
p.CloseMainWindow();
doneSomething = true;
NihLog.Write(NihLog.Level.Info, "Killed native U9 app");
System.Threading.Thread.Sleep(1000); // Will not wait if no native app is started
}
} while (doneSomething);
}
void WriteToModem(string s)
{
modemPort.Write(s);
NihLog.Write(NihLog.Level.Debug, "TX " + s);
}
void UninitModem()
{
if (modemPort != null)
modemPort.Dispose();
modemPort = null;
CurrentModemState = ModemState.NotInitialized;
}
private bool IsTransitionalState(ModemState ms)
{
return ms == ModemState.Initializing
|| ms == ModemState.SendingSMS
|| ms == ModemState.PowerUp
|| ms == ModemState.KeepAliveAwaitingResponse;
}
Timeouter _initFailureTimeout =
new Timeouter(timeout_s: 10, autoReset:false, timedOut:true);
void TryInitModem()
{
// Try pistoning the modem with higher frequency. This does no harm (such as redundant logging)
PrepareU9Modem(); // Will do nothing if modem is okay
if (!_initFailureTimeout.IsTimedOut())
return; // Don't try too frequently
if (modemPort != null)
return;
const string modemPortName = "Basecom HS-USB NMEA 9000";
const int speed = 115200;
string portName = Hardware.Misc.SerialPortFromFriendlyName(modemPortName);
if (portName == null)
{
NihLog.Write(NihLog.Level.Error, "Modem not found (yet). ");
_initFailureTimeout.Reset();
return;
}
NihLog.Write(NihLog.Level.Info, string.Format("Found modem port \"{0}\" at {1}", modemPortName, portName));
modemPort = new System.IO.Ports.SerialPort(portName, speed);
modemPort.ReadTimeout = 3000;
modemPort.NewLine = "\r\n";
modemPort.Open();
modemPort.DiscardInBuffer();
modemPort.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(delegate { _mainThreadOwnder.Invoke(dataReceivedDelegate); }); // called in different thread!
_lastModemKeepAlive = new Timeouter(60, true);
WriteToModem("AT+CFUN=1\r");
CurrentModemState = ModemState.PowerUp;
}
void CheatU9Telit()
{
// U9 telit appears as USB CDrom on the bus, until disk eject is sent.
// Then, it reappears as normal stuff.
VolumeDeviceClass volumeDeviceClass = new VolumeDeviceClass();
foreach (Volume device in volumeDeviceClass.Devices)
{
if (device.FriendlyName == "PCL HSUPA Modem USB Device")
{
NihLog.Write(NihLog.Level.Info, "Trying to initialize: " + device.FriendlyName);
device.EjectCDRomNoUI();
}
}
}
void PrepareU9Modem()
{
CloseU9TelitNativeApp(); // Closes the autorun native app
CheatU9Telit();
}
}
public class OutboundSMS
{
public string Destination;
public string Text;
public int MaxTries;
public int Attempt = 0;
public OutboundSMS(string dest, string txt)
{
Destination = dest;
Text = txt;
MaxTries = 3;
}
override public string ToString()
{
if(Attempt > 1)
return string.Format("\"{0}\" to {1}, attempt {2}",
Text, Destination, Attempt);
else
return string.Format("\"{0}\" to {1}",
Text, Destination);
}
}
public class InboundSMS
{
public string SourcePhone;
public DateTime ReceiveTime;
public string Text;
public void ParseCMT(string line1, string line2)
{
string[] fields = line1.Split(new char[] { ',', ' ', '/', ':', '"' });
if (fields.Length < 12)
throw new ApplicationException("CMT message too short. Expected 2 fields");
SourcePhone = fields[3];
ReceiveTime = DateTime.Parse("20" + fields[7] + "-" + fields[8] + "-" + fields[9] + " " + fields[10] + ":" + fields[11] + ":" + fields[12].Substring(0, 2)); //test carefully
Text = line2;
}
};
}
Usage example in a winforms application:
Hardware.TeliaModem _modem;
public Form1()
{
InitializeComponent();
_modem = new Hardware.TeliaModem(this);
_modem.OnNewSMS += new Hardware.TeliaModem.NewSmsHandler(ProcessNewSMS);
_timer.Interval = 1000; // milliseconds
_timer.Tick += new EventHandler(OnTimerTick);
_timer.Start();
}
To send an SMS:
_modem.SendSMS(sms.SourcePhone, "Aircon is now " + BoolToString.ON_OFF(_aircon.On));
On timer event, once every second:
private void OnTimerTick(object o, EventArgs e)
{
_modem.HeartBeat();
}
This is registered as Winforms timer handler, so that modem will not have a thread.
Some utility classes used:
namespace Utils
{
public class StopWatch
{
protected DateTime _resetTime;
public StopWatch() { Reset(); }
public void Reset() { _resetTime = DateTime.Now; }
public double TotalSeconds { get { return (DateTime.Now - _resetTime).TotalSeconds; } }
public TimeSpan Time { get { return DateTime.Now - _resetTime; } }
};
public class Timeouter : StopWatch
{
private bool _autoReset;
private double _timeout_s;
public Timeouter(double timeout_s, bool autoReset, bool timedOut = false)
{
_timeout_s = timeout_s;
_autoReset = autoReset;
if (timedOut)
_resetTime -= TimeSpan.FromSeconds(_timeout_s + 1); // This is surely timed out, as requested
}
public bool IsTimedOut()
{
if (_timeout_s == double.PositiveInfinity)
return false;
bool timedout = this.TotalSeconds >= _timeout_s;
if (timedout && _autoReset)
Reset();
return timedout;
}
public void Reset(double timeout_s) { _timeout_s = timeout_s; Reset(); }
public double TimeLeft { get { return _timeout_s - TotalSeconds; } }
}
}
I implemented this in order to turn on air conditioner with SMS (yeah, I live in a hot country). It works. Please feel free to use any of this code.
Enjoy.
Microsoft provides a pretty decent VB.Net example in a KB article "How to access serial and parallel ports by using Visual Basic .NET". I'm not sure what your phone returns, however, if you change the buffer loop in the example, you can at least print out the reply:
MSComm1.Output = "AT" & Chr(13)
Console.WriteLine("Send the attention command to the modem.")
Console.WriteLine("Wait for the data to come back to the serial port...")
' Make sure that the modem responds with "OK".
' Wait for the data to come back to the serial port.
Do
Buffer = Buffer & MSComm1.Input
Console.WriteLine("Found: {0}", Buffer)
Loop Until InStr(Buffer, "OK" & vbCrLf)
GSMComm is a C# library that will allow you to easily interact with a GSM mobile in text or PDU mode. (The site also has a bunch of testing utilities)
Working with GSM modem to send SMS has never been a good idea as such. Here's a checklist to make sure your SMS is sent out properly.
You are sending the SMS in text mode. Try sending it in PDU mode.
Try this library http://phonemanager.codeplex.com/
Do you change SMSC number before sending out the SMS, if so make sure the SMSC is correct.
Also make sure your SIM card has enough credits to let you send outbound SMS.
Check the network status especially when you're sending out SMS from your program. This might be a problem.
I recommend you checkout the Lite edition of NowSMS gateway that comes for free and lets you work with GSM modem very continently. NowSMS will give you api abstraction over a GSM modem connection, so you wil just need to call the Http Api of NowSMS gateway, rest will be taken care by the NowSMS gateway.
Above all this, if your end-users are going to remain internet connected while using your application or if your application is web-based and hosted over internet, I strongly recoomend to do away with GSM modem option and opt-in for reliable Http SMS gateway service providers.

C# check if a COM (Serial) port is already open

Is there an easy way of programmatically checking if a serial COM port is already open/being used?
Normally I would use:
try
{
// open port
}
catch (Exception ex)
{
// handle the exception
}
However, I would like to programatically check so I can attempt to use another COM port or some such.
I needed something similar some time ago, to search for a device.
I obtained a list of available COM ports and then simply iterated over them, if it didn't throw an exception i tried to communicate with the device. A bit rough but working.
var portNames = SerialPort.GetPortNames();
foreach(var port in portNames) {
//Try for every portName and break on the first working
}
This is how I did it:
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode, IntPtr securityAttrs, int dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile);
then later on
int dwFlagsAndAttributes = 0x40000000;
var portName = "COM5";
var isValid = SerialPort.GetPortNames().Any(x => string.Compare(x, portName, true) == 0);
if (!isValid)
throw new System.IO.IOException(string.Format("{0} port was not found", portName));
//Borrowed from Microsoft's Serial Port Open Method :)
SafeFileHandle hFile = CreateFile(#"\\.\" + portName, -1073741824, 0, IntPtr.Zero, 3, dwFlagsAndAttributes, IntPtr.Zero);
if (hFile.IsInvalid)
throw new System.IO.IOException(string.Format("{0} port is already open", portName));
hFile.Close();
using (var serialPort = new SerialPort(portName, 115200, Parity.None, 8, StopBits.One))
{
serialPort.Open();
}
For people that cannot use SerialPort.GetPortNames(); because they are not targeting .net framework (like in my case I am using .Net Core and NOT .Net Framework) here is what I ended up doing:
In command prompt if you type mode you get something like this:
mode is an executable located at C:\Windows\System32\mode.com. Just parse the results of that executable with a regex like this:
// Code that answers the question
var proc = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = #"C:\Windows\System32\mode.com",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
proc.Start();
proc.WaitForExit(4000); // wait up to 4 seconds. It usually takes less than a second
// get ports being used
var output = proc.StandardOutput.ReadToEnd();
Now if you want to parse the output this is how I do it:
List<string> comPortsBeingUsed = new List<string>();
Regex.Replace(output, #"(?xi) status [\s\w]+? (COM\d) \b ", regexCapture =>
{
comPortsBeingUsed.Add(regexCapture.Groups[1].Value);
return null;
});
foreach(var item in comPortsBeingUsed)
{
Console.WriteLine($"COM port {item} is in use");
}
I wanted to open the next available port and did it like this.
Please note, is it not for WPF but for Windows Forms.
I populated a combobox with the com ports available.
Then I try to open the first one. If it fails, I select the next available item from the combobox. If the selected index did not change in the end, there were no alternate com ports available and we show a message.
private void GetPortNames()
{
comboBoxComPort.Items.Clear();
foreach (string s in SerialPort.GetPortNames())
{
comboBoxComPort.Items.Add(s);
}
comboBoxComPort.SelectedIndex = 0;
}
private void OpenSerialPort()
{
try
{
serialPort1.PortName = comboBoxComPort.SelectedItem.ToString();
serialPort1.Open();
}
catch (Exception ex)
{
int SelectedIndex = comboBoxComPort.SelectedIndex;
if (comboBoxComPort.SelectedIndex >= comboBoxComPort.Items.Count - 1)
{
comboBoxComPort.SelectedIndex = 0;
}
else
{
comboBoxComPort.SelectedIndex++;
}
if (comboBoxComPort.SelectedIndex == SelectedIndex)
{
buttonOpenClose.Text = "Open Port";
MessageBox.Show("Error accessing port." + Environment.NewLine + ex.Message, "Port Error!!!", MessageBoxButtons.OK);
}
else
{
OpenSerialPort();
}
}
if (serialPort1.IsOpen)
{
StartAsyncSerialReading();
}
}
The SerialPort class has an Open method, which will throw a few exceptions.
The reference above contains detailed examples.
See also, the IsOpen property.
A simple test:
using System;
using System.IO.Ports;
using System.Collections.Generic;
using System.Text;
namespace SerPort1
{
class Program
{
static private SerialPort MyPort;
static void Main(string[] args)
{
MyPort = new SerialPort("COM1");
OpenMyPort();
Console.WriteLine("BaudRate {0}", MyPort.BaudRate);
OpenMyPort();
MyPort.Close();
Console.ReadLine();
}
private static void OpenMyPort()
{
try
{
MyPort.Open();
}
catch (Exception ex)
{
Console.WriteLine("Error opening my port: {0}", ex.Message);
}
}
}
}
Sharing what worked for me (a simple helper method):
private string portName { get; set; } = string.Empty;
/// <summary>
/// Returns SerialPort Port State (Open / Closed)
/// </summary>
/// <returns></returns>
internal bool HasOpenPort()
{
bool portState = false;
if (portName != string.Empty)
{
using (SerialPort serialPort = new SerialPort(portName))
{
foreach (var itm in SerialPort.GetPortNames())
{
if (itm.Contains(serialPort.PortName))
{
if (serialPort.IsOpen) { portState = true; }
else { portState = false; }
}
}
}
}
else { System.Windows.Forms.MessageBox.Show("Error: No Port Specified."); }
return portState;
}
Notes:
- For more advanced technique(s) I recommend using ManagementObjectSearcher Class.
More info Here.
- For Arduino devices I would leave the Port Open.
- Recommend using a Try Catch block if you need to catch exceptions.
- Check also: "TimeoutException"
- More information on how to get SerialPort (Open) Exceptions Here.
public void MobileMessages(string ComNo, string MobileMessage, string MobileNo)
{
if (SerialPort.IsOpen )
SerialPort.Close();
try
{
SerialPort.PortName = ComNo;
SerialPort.BaudRate = 9600;
SerialPort.Parity = Parity.None;
SerialPort.StopBits = StopBits.One;
SerialPort.DataBits = 8;
SerialPort.Handshake = Handshake.RequestToSend;
SerialPort.DtrEnable = true;
SerialPort.RtsEnable = true;
SerialPort.NewLine = Constants.vbCrLf;
string message;
message = MobileMessage;
SerialPort.Open();
if (SerialPort.IsOpen )
{
SerialPort.Write("AT" + Constants.vbCrLf);
SerialPort.Write("AT+CMGF=1" + Constants.vbCrLf);
SerialPort.Write("AT+CMGS=" + Strings.Chr(34) + MobileNo + Strings.Chr(34) + Constants.vbCrLf);
SerialPort.Write(message + Strings.Chr(26));
}
else
("Port not available");
SerialPort.Close();
System.Threading.Thread.Sleep(5000);
}
catch (Exception ex)
{
message.show("The port " + ComNo + " does not exist, change port no ");
}
}
I have been fighting with this problem for a few weeks now. Thanks to the suggestions on here and from the site, https://www.dreamincode.net/forums/topic/91090-c%23-serial-port-unauthorizedaccessexception/ .
I finally came up with a solution that seems to work.
The application I am working on allows a user to connect to a USB device and display data from it.
The Problem I was battling. Along side the application I am writing, I use another serial terminal application for doing my testing. Sometimes I forget to disconnect the COMport being used on the other application. If I do, and try to connect with the application I am writing, I would get an “UnAuthorizedAccessException” error. Along with this exception came some side effects, such as double lines of data being spit out and the application locking up on closing down.
My Solution
Thanks to the advice on here and the other site referenced, this was my solution.
private void checkAndFillPortNameList()
{
SerialPort _testingSerialPort;
AvailablePortNamesFound.Clear();
List<string> availablePortNames = new List<string>();//mySerial.GetAvailablePortNames();
foreach (string portName in SerialPortDataAccess.GetAvailablePortNames())
{
try
{
_testingSerialPort = new SerialPort(portName);
_testingSerialPort.Open();
if (_testingSerialPort.IsOpen)
{
availablePortNames.Add(portName);
_testingSerialPort.Close();
}
}
catch (Exception ex)
{
}
}
availablePortNames.Sort();
AvailablePortNamesFound = new ObservableCollection<string>(availablePortNames);
}
This routine connects to a combobox which holds the available Comports for selection. If a Comport is already, in use by another application, that port name will not appear in the combo box.
You can try folloing code to check whether a port already open or not. I'm assumming you dont know specificaly which port you want to check.
foreach (var portName in Serial.GetPortNames()
{
SerialPort port = new SerialPort(portName);
if (port.IsOpen){
/** do something **/
}
else {
/** do something **/
}
}

Categories