I'm running Windows version 10.0.16299.0, and building on Visual Studio C# 2017. I can successfully connect to an unpaired BLE device from a Windows Forms app, and get ValueChanged events (1 per second), but not for long. I usually stop receiving those events in 40 seconds or less - usually less.
I realize this is likely a dispose/GC issue, but I don't see how. The device, service, characteristics, and descriptors are all stored as member variables in the main form and should not get collected:
public partial class Form1 : Form
{
private BluetoothLEDevice _device;
private List<GattDeviceService> _services;
private List<GattDescriptor> _descriptors = new List<GattDescriptor>();
private List<GattCharacteristic> _characteristics = new List<GattCharacteristic>();
private async void button1_Click(object sender, EventArgs e)
{
_device = await BluetoothLEDevice.FromIdAsync("BluetoothLE#BluetoothLE00:xx:xx:xx:xx:xx:xx:xx:xx:xx");
var services = await _device.GetGattServicesAsync();
foreach (var service in services.Services)
{
var chars = await service.GetCharacteristicsAsync();
foreach (var ch in chars.Characteristics)
{
var descriptors = await ch.GetDescriptorsAsync();
foreach (var desc in descriptors.Descriptors)
{
if (desc.AttributeHandle == 15 || desc.AttributeHandle == 26)
{
_services.Add(service);
_descriptors.Add(desc);
_characteristics.Add(ch);
var writer = new DataWriter();
writer.WriteBytes(new byte[] { 1, 0 });
var buf = writer.DetachBuffer();
await desc.WriteValueAsync(buf);
}
ch.ValueChanged += ChOnValueChanged;
}
}
}
}
In my sample, I click a button to establish a connection and subscribe to events. Before you say that writing to the descriptor is not how you would do it - I know. The device uses non-standard descriptor IDs which is why I must write to them directly.
Note that everything works, including the writes - I get no errors. It's just that the ValueChanged event is no longer fired after a short duration, and I can't figure out what else I must "cache" in order to prevent objects from being disposed, assuming that's what the problem is.
The problem is that because of the nested for each iterations you attach the characteristic_changed_event to multiple characteristics. That leads to unwanted behaviour.
The best way is to select the service that contains the wanted characteristic by UUID, then select the characteristic by UUID from that service.
If you insist on filtering by the wanted descriptor attribute handle,
finish all the "for each-es" before attaching the characteristic_changed_event.
The characteristic to attach to is probably first in _characteristics list.
Related
I wanted to create a load balancing in Kafka (multiple programming languages) for a topic. So I did the following.
Created a topic with 4 partitions.
Created a producer in C# (producing messages every second)
Created one consumer(consumer1) in C# (consumer group: testConsumerGrp)
Created one more consumer(consumer2) in NodeJs (consumer group: testConsumerGrp)
I used confluent.kafka in C# and kafkajs in NodeJs.
I Open the producer and keep it running.
If I run only C# consumer, it works fine.
If I run only NodeJs consumer, it works fine.
If I run multiple C# consumer (only c# and less than 4 instances), it works fine.
If I run multiple NodeJs consumer (only NodeJs and less than 4 instances), it works fine.
If I run one C# and one NodeJs consumer then I am getting Inconsistent group protocol error
Can't we use two programming languages for a same consumer group?
Producer in C# - windows form
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using Confluent.Kafka;
namespace KafkaProducer
{
public partial class frmProducer : Form
{
const string TOPIC = "testTopic";
private IProducer<Null, string> pBuilder;
public frmProducer()
{
InitializeComponent();
}
private async void timer1_Tick(object sender, EventArgs e)
{
try
{
// instead of sending some value, we send current DateTime as value
var dr = await pBuilder.ProduceAsync(TOPIC, new Message<Null, string> { Value = DateTime.Now.ToLongTimeString() });
// once done, add the value into list box
listBox1.Items.Add($"{dr.Value} - Sent to Partition: {dr.Partition.Value}");
listBox1.TopIndex = listBox1.Items.Count - 1;
}
catch (ProduceException<Null, string> err)
{
MessageBox.Show($"Failed to deliver msg: {err.Error.Reason}");
}
}
private void frmProducer_Load(object sender, EventArgs e)
{
ProducerConfig config = new ProducerConfig { BootstrapServers = "localhost:9092" };
pBuilder = new ProducerBuilder<Null, string>(config).Build();
timer1.Enabled = true;
}
private void frmProducer_FormClosing(object sender, FormClosingEventArgs e)
{
timer1.Enabled = false;
pBuilder.Dispose();
}
}
}
Consumer in C# - windows form
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Confluent.Kafka;
namespace KafkaConsumer
{
public partial class frmConsumer : Form
{
CancellationTokenSource cts = new CancellationTokenSource();
public frmConsumer()
{
InitializeComponent();
}
private void StartListen()
{
var conf = new ConsumerConfig
{
GroupId = "test-consumer-group",
BootstrapServers = "localhost:9092",
AutoOffsetReset = AutoOffsetReset.Earliest
};
using (var c = new ConsumerBuilder<Ignore, string>(conf).Build())
{
c.Subscribe("testTopic");
//TopicPartitionTimestamp tpts = new TopicPartitionTimestamp("testTopic", new Partition(), Timestamp. )
//c.OffsetsForTimes()
try
{
while (true)
{
try
{
var cr = c.Consume(cts.Token);
// Adding the consumed values into the UI
listBox1.Invoke(new Action(() =>
{
listBox1.Items.Add($"{cr.Value} - from Partition: {cr.Partition.Value}" );
listBox1.TopIndex = listBox1.Items.Count - 1;
}));
}
catch (ConsumeException err)
{
MessageBox.Show($"Error occured: {err.Error.Reason}");
}
}
}
catch (OperationCanceledException)
{
// Ensure the consumer leaves the group cleanly and final offsets are committed.
c.Close();
}
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
cts.Cancel();
}
private async void frmConsumer_Load(object sender, EventArgs e)
{
await Task.Run(() => StartListen());
}
}
}
Consumer in NodeJs
const { Kafka } = require("kafkajs");
const kafka = new Kafka({
clientId: 'my-app',
brokers: ["localhost:9092"]
});
const consumer = kafka.consumer({ groupId: "test-consumer-group" });
const run = async () => {
// Consuming
await consumer.connect();
await consumer.subscribe({ topic: "testTopic", fromBeginning: false });
await consumer.run({
eachMessage: async ({ topic, partition, message }) => {
console.log(message.value.toString() + " - from Partition " + partition);
}
});
};
run().catch(console.error);
If I run C# and NodeJs consumer at same time then getting Inconsistent group protocol error.
How to use multiple consumer from different programming languages in Kafka?
Short answer:
This may not have as much to do with the different languages as you might think. This is happening due to the differences in the protocols of the 2 consumer clients (and their libraries).
Try setting the following property in both the consumer clients:
partition.assignment.strategy = round-robin
Note: I've just supplied the general property so you'll need to look at the language specific versions for your clients. You could even set this to range but keep it consistent.
The explanation goes like this:
Reading through the protocol on Kafka's wiki to find out the root cause of Inconsistent group protocol - it turns out that this is returned when:
There is an active consumer group with active/running consumers
And a new consumer arrives to join this group with a protocol type (or a set of protocols) that is not compatible with that of the current group
Now, there could be various aspects in the ConsumerGroupProtocolMetadata but one of the aspects that does seem to differ in the libraries of the clients that you're using is the partition.assignment.strategy.
The dotnet client is a wrapper around librdkafka defaults the value of the above property to range. Here's the reference.
where as
kafkajs as per the documentation defaults it to round-robin - hence causing the inconsistency.
Hope this helps.
I know this comes one year too late but this happens because of the same group naming
When you start the C# client it creates a group for its consumers.
E.g. group-1 (group-1-consumer-1,group-1-consumer-2, etc) - These names are automatically allocated so don't bother. I think you can set these manually but is not recommended to avoid potential name collision.
Now, when you set this in motion you cannot add the same group from a different group runner (from another microservice).
See what Lalit quoted from Kafka wiki:
There is an active consumer group with active/running consumers
Now, when you will start the nodeJs one, you should use a different group name as most likely will carry out other tasks with that data.
Yes, you can subscribe both groups to the same topics as Kafka will keep an offset for each group and where they left of.
I have two console apps written with C#. I'm trying to exchange messages between them. In an attempt to do this, I'm using non-persisted memory-mapped files. In my scenario, one console app is the parent and the other is the child. Sometimes, the parent will send a message to the child. Other times, the child will send a message to the parent.
I can't figure out how to do this though. It's like each process is either listening or speaking. It's not actively doing both. At this time, I'm trying to exchange messages between the two processes using a struct that's defined as this:
public struct Message
{
public string Source { get; set; }
public string Text { get; set; }
}
My parent console app has a method that looks like this:
private void SendMessageToChild(string text, int childHandle)
{
Console.WriteLine("Sending message to child...");
var messageChannelFileName = childHandle.ToString() + ".msgs";
using (var messageChannelFile = MemoryMappedFile.CreateOrOpen(messageChannelFileName, 10240))
{
using (var memoryMappedAccessor = messageChannelFile.CreateViewAccessor())
{
var message = new Message();
message.Text = text;
message.Source = "Parent";
memoryMappedAccessor.Write<Message>(0, ref message);
}
}
Console.ReadKey(); // This is to keep the memory mapped file open for the child to be able to read it
Console.WriteLine("Successfully sent message to child.");
}
My child console app (process) has a method that looks like this:
private void StartListening()
{
Task.Run(() =>
{
var messageChannelFileName = Process.GetCurrentProcess().Id + ".msgs";
using (var messageChannelFile = MemoryMappedFile.OpenExisting(messageChannelFileName, MemoryMappedFileRights.Read))
{
var message = new Message();
using (var messageAccessor = messageChannelFile.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read))
{
messageAccessor.Read<Message>(0, out message);
Console.WriteLine(message.Text);
}
}
Console.ReadKey(); // This is to keep the memory mapped file
});
}
This approach isn't working. I never see the message printed to the console window. At the same time, I do not see a way to send messages back-and-forth. In my opinion, the Console.ReadKey required in both sides locks the file.
Am I misunderstanding something? Am I using the wrong thing to exchange messages between two processes? I know I can't use Pipes for my scenario, which is why I went with memory mapped files.
Its really easy to communicate between 2 processes
For example Parent process do that :
// create EventWaitHandle, MemoryMapped and accessor
ewh = new EventWaitHandle(false, EventResetMode.AutoReset, "ewhFreePIE");
memory = MemoryMappedFile.CreateOrOpen("hookFreePIE", 68, MemoryMappedFileAccess.ReadWrite);
accessor = memory.CreateViewAccessor();
:
:
// Send message with accessor.write
ewh.Set();//say to other process, there is something to read
Example of Child Process:
memory = MemoryMappedFile.CreateOrOpen("hookFreePIE", 68, MemoryMappedFileAccess.ReadWrite);
accessor = memory.CreateViewAccessor();
ewh = EventWaitHandle.OpenExisting("ewhFreePIE");
:
:
// sample of loop
public void StartLoop()
{
while (running)
{
ewh.WaitOne();// wait Set() of another or same process
if (cmdtostop) //you could create cmdstop inside memorymapped file (set first byte to 1 for example
{
running = false;
}
else
{
//do something with data , using accessor.Read
}
}
if you want child send to parent you create another EventWaithandle and do the same thing from child to Parent
Dont forget to dispose resource when process finish
I'm developing a c# desktop api with forms where I want to receive ACC data from a BLE server und display them in a chart.
So I'm running in a connection problem and I can't find any solution.
I can find my LE server Device with the watcher.
DevicePairingResult dpr = await device.DeviceInformation.Pairing.PairAsync(DevicePairingProtectionLevel.Encryption);
returns me "AlreadyPaired"
But when I do
device = await BluetoothLEDevice.FromBluetoothAddressAsync(bluetoothAddress: eventArgs.BluetoothAddress);
mGattService = device.GetGattService(MotionService_GUID);
mCharacteristic = mGattService.GetCharacteristics(ACC_Characteristic_GUID)[0];
and then
var con = device.ConnectionStatus;
I receive "Disconnected" in con.
I am bound with de device on windows( I searched for it in Windows and entered the Code) but I am not connected(based on the Status in the windows info center).
I've read in another Thread in the windows c# developer page that it should not be necessary anymore to pair the device manually.
I'm pretty shure that the rest of my code works because sometimes I can get a connection( pretty confusing for me) and see the right Data in my chart.
Right now I just want to reach a stable connection before changing other part of my code.
Anyone any idea how to solve this?
Thx medTech
Edit:
Here is part of the Code:
Scanning for BLE
private void button1_Click(object sender, EventArgs e)
{
// Create Bluetooth Listener
var watcher = new BluetoothLEAdvertisementWatcher();
watcher.ScanningMode = BluetoothLEScanningMode.Active;
// Register callback for when we see an advertisements
watcher.Received += OnAdvertisementReceivedAsync;
// Wait 5 seconds to make sure the device is really out of range
watcher.SignalStrengthFilter.OutOfRangeTimeout = TimeSpan.FromMilliseconds(5000);
watcher.SignalStrengthFilter.SamplingInterval = TimeSpan.FromMilliseconds(2000);
// Starting watching for advertisements
watcher.Start();
}
Connect to Server:
private async void OnAdvertisementReceivedAsync(BluetoothLEAdvertisementWatcher watcher, BluetoothLEAdvertisementReceivedEventArgs eventArgs)
{
// Filter for specific Device
if (eventArgs.Advertisement.LocalName == "MYDEVICE")
{
watcher.Stop();
var MotionService_GUID = new Guid("00002000-0000-1000-8000-00805F9B34FB");
var ACC_Characteristic_GUID = new Guid("00002001-0000-1000-8000-00805F9B34FB");
device = await BluetoothLEDevice.FromBluetoothAddressAsync(bluetoothAddress: eventArgs.BluetoothAddress);
DevicePairingResult dpr = await device.DeviceInformation.Pairing.PairAsync(DevicePairingProtectionLevel.Encryption);
mGattService = device.GetGattService(MotionService_GUID);
mCharacteristic = mGattService.GetCharacteristics(ACC_Characteristic_GUID)[0];
GattDeviceServicesResult result = await device.GetGattServicesAsync();
GattCommunicationStatus status1 = await ReadFromCharacteristicAsync(mCharacteristic);
var con = device.ConnectionStatus;
while (status1 == GattCommunicationStatus.Success)
{
try
{
status1 = await ReadFromCharacteristicAsync(mCharacteristic);
}
catch
{
Console.WriteLine("ERROR");
status1 = GattCommunicationStatus.Unreachable;
}
}
}
}
Read from Characteristic:
async Task ReadFromCharacteristicAsync(GattCharacteristic mCharacteristic)
{
GattReadResult readResult = await mCharacteristic.ReadValueAsync(BluetoothCacheMode.Uncached);
if (readResult.Status == GattCommunicationStatus.Success)
{
byte[] data = new byte[readResult.Value.Length];
DataReader.FromBuffer(readResult.Value).ReadBytes(data);
if (chart1.IsHandleCreated)
{
this.Invoke((MethodInvoker)delegate { updateChart(data); });
}
return readResult.Status;
}
return readResult.Status;
}
Terminate Connection
private async Task<bool> ClearBluetoothLEDeviceAsync()
{
mCharacteristic.Service.Dispose();
mGattService.Dispose();
await device.DeviceInformation.Pairing.UnpairAsync();
device?.Dispose();
device = null;
GC.Collect();
return true;
}
SO now when I connect the first time to the Server, I only receive zeros which shows me that the there might be a authentication Error.
After that I always receive this Error:
"System.ArgumentException" in mscorlib.dll with a notification that there is noch executable Code left because all Threads are doing some asynchronous stuff.
This Error gets thrown when I try to read from the Characteristic.
I never coded in c# before so I am not shure if there is an error in my asynchronous part oder the communication part.
Thanks you
Pairing is not the same as connecting!
I really advise using the BLE-advertisementWatcher to select and connect to your device.
The reason is that many BLE-devices don't save their pairing status.
In windows device-watcher once paired, the device stays paired even if it is switched off or out of reach.
Also many times the connection status is kept, unless the device is unpaired and disposed in code or removed in windows settings.
All BLE-devices that I know of start advertising as soon as there is no connection for some time.
This time depends on the device, but most of the time within seconds.
So don't pair but just connect if the device is advertising.
I am working on an ASP.NET Webform project (legacy code).On my button_click event i am sending sms message to all the datas populated in this.
var customerSMS = BusinessLayer.SMS.SmsSetup.GetAllCustomerSMS(OfficeId);
This takes around 15seconds to do all the computing and get the data(1000rows)
from the Db.And for each data it runs through the loop and does validation and
sends the sms and it does take time.I want to do this task in background and
redirect the user to the index page and the background process continues till it
gets out of the loop.I am new to this and still learning this beautiful
language C#.I did go through this amazing Asynchronous Programming async/await
and Multithreading approach and got hold of it only in simple WindowsForm
applications.Any reference/code snippet/best approach with a simple explanation for my case would be helpful.
My button click event code :
protected void ReturntoDashboard_Click(object sender, EventArgs e)
{
sms = Everest.Net.BusinessLayer.SMS.SmsSetup.GetSmsSetUp(OfficeId);
if (sms.EnableSmsData && sms.SmsCount > 0)
{
#region Loan Section
var smsLoan = Everest.Net.BusinessLayer.SMS.SmsSetup.GetLoanId(s.Sms_AccountNumber);
var loanId =
BusinessLayer.SMS.SmsSetup.GetLoanIdValue(s.Sms_AccountNumber);
var dateexceeded =
BusinessLayer.SMS.SmsSetup.IsDateExceeded(loanId);
if (smsLoan != null && dateexceeded == true)
{
foreach (Common.SMS.SMSSetup sm in smsLoan)
{
var smsClosingBalanceLoan = BusinessLayer.SMS.SmsSetup.GetAmountForLoanAlert( sm.LoanId,
BusinessLayer.Core.DateConversion
.GetCurrentServerDate()
.AddDays(sms.DaysbeforeLoanalerts).ToString());
if (smsClosingBalanceLoan != null)
{
if (smsClosingBalanceLoan.LoanAmountToPay > 0)
{
int smsSentAlertCount = sms.LoanAlertCount;
var logCount = BusinessLayer.SMS.SmsSetup.GetLoanSmsAlertSentCount(DateTime.Now.AddDays(-smsSentAlertCount).ToString("yyyy-MM-dd"), DateTime.Now.ToString("yyyy-MM-dd"), sm.LoanAccountNumber);
if (logCount < smsSentAlertCount)
{
smsLog = new Everest.Net.Common.SMS.SMSSetup();
finalMessage = "Dear Member, Your Loan accnt " + sm.LoanAccountNumber + " with Principal"+ "+" + "Int Amnt: Rs." + smsClosingBalanceLoan.LoanAmountToPay + " need to be payed.Thank You," + officeName.OfficeName;
smsLog.LogServiceType = "Loan";
smsLog.LogSmsType = s.Sms_SmsType;
smsLog.LogSmsMessage = finalMessage;
smsLog.LogCustomerId = s.CustomerId.ToString();
smsLog.LogAccountNumber = s.Sms_AccountNumber;
smsLog.LogAccountType = s.Sms_AccountType;
smsLog.LogSmsSentDate = BusinessLayer.Core.DateConversion.GetCurrentServerDate();
smsLog.LogSmsFailedDate = "";
smsLog.LogSentStatus = true;
smsLog.LogUserId = UserId;
smsLog.LogSmsFailedMessage = "";
try
{
var result = Everest.Net.BusinessLayer.SMS.smsParameters.SendSMS(sms.FromNum, sms.Token, sms.Url, cellNum, finalMessage);
}
catch (Exception ex)
{
smsLog.LogSmsFailedDate = System.DateTime.Now.ToString("MM/dd/yyyy HHmmss");
smsLog.LogSentStatus = false;
smsLog.LogSmsFailedMessage = ex.Message;
Everest.Net.BusinessLayer.SMS.SmsSetup.InsertSMSLog(smsLog);
}
sms = Everest.Net.BusinessLayer.SMS.SmsSetup.GetSmsSetUp(OfficeId);
sms.SmsCount = sms.SmsCount - 1;
Everest.Net.BusinessLayer.SMS.SmsSetup.UpdateSmsSetup(sms);
Everest.Net.BusinessLayer.SMS.SmsSetup.InsertSMSLog(smsLog);
}
}
}
}
}
}
}
}
catch (Exception ex)
The ideal solution would remove the responsibility of sending the SMS from the web application itself. Instead, the web application should create a database record containing the message and recipient addresses, and a separate background job (e.g. a Windows Service) should poll the database and send SMS messages when neeeded. This is the best solution in terms of fault tolerance and auditability, because there is a permanent record of the messaging job which can be resumed if the system fails.
That being said, maybe you don't want to go to all that trouble. If you feel strongly that you wish to send the SMS directly from the ASP.NET application, you will need to create a Task and queue it to run using QueueBackgroundWorkitem. You will need to refactor your code a bit.
Move all the logic for sending the SMS into a separate function that accepts all the information needed as parameters. For example,
static void SendSMS(string[] addresses, string messagetext)
{
//Put your SMS code here
}
When you need to call the function, queue it as a background item
HostingEnvironment.QueueBackgroundWorkItem(a => SendSMS(addresses, messageText));
If your worker task needs to access its own cancellation token (e.g. if it is supposed to loop until cancelled), it is passed as an argument to the lambda expression. So you could modify the prototype
static void SendSMS(string[] addresses, string messagetext, CancellationToken token)
{
while (!token.IsCancellationRequested)
{
//Put your code here
}
}
and pass it thus:
HostingEnvironment.QueueBackgroundWorkItem(token => SendSMS(addresses, messageText, token));
Placing the task in the background queue ensures that ASP.NET keeps track of the thread, doesn't try to garbage collect it, and shuts it down properly when the application pool needs to shut down.
After queuing the background operation, your page can render is content per usual and conclude the HTTP response while the task continues to execute.
In the way of learning BLE programming using C#, I'm trying to write a winform application that should detec BLE devices and use them as proximity sensors. For this purpose I've applied the BluetoothAdvertisementWatcher class, filtering the advertisements with a provided signal strength. The problem is the detection seems to be quite crappy... Even with the device at no distance from my PC, the listener often returns a RSSI value of -127, wich stands for "no detection" as I've understood. I've tried setting the OutOfRangeTimeout to 10 seconds and, even if things get better, problems are still heavy. The major issue is the detection can flows without interruption just for about a bunch of seconds, then the debugger signals me a thread exiting with code 0, and the listener can't receive new advertisements (with the device ever aside the pc) for even 30-40 seconds, then it restarts and so it loops.
Do you know if is this how it is intended to work or if am I missing something? Is there a way, eventually, to intercept this interruption and immediately restart the listener?
This is how I initialize the watcher:
public BLEScanner(short maxDBRange, TimeSpan outOfRangeTimeout) {
this.InRange = maxDBRange;
this.OutOfRange = (short)(this.InRange + BLEScanner.BUFFER_RANGE);
this.OutOfRangeTimeout = outOfRangeTimeout;
this.watcher = new BluetoothLEAdvertisementWatcher();
this.watcher.SignalStrengthFilter.InRangeThresholdInDBm = this.InRange;
this.watcher.SignalStrengthFilter.OutOfRangeThresholdInDBm = this.OutOfRange;
this.watcher.SignalStrengthFilter.OutOfRangeTimeout = this.OutOfRangeTimeout;
this.watcher.SignalStrengthFilter.SamplingInterval = TimeSpan.FromSeconds(1);
this.watcher.ScanningMode = BluetoothLEScanningMode.Active;
}
While this is the event handler:
private async void OnAdvertisementReceived(BluetoothLEAdvertisementWatcher watcher, BluetoothLEAdvertisementReceivedEventArgs eventArgs) {
var deviceAddress = eventArgs.BluetoothAddress;
BluetoothLEDevice device = await BluetoothLEDevice.FromBluetoothAddressAsync(deviceAddress);
UpdateUiDelegate update = new UpdateUiDelegate(
(dev, args) => {
if (eventArgs.RawSignalStrengthInDBm == -127) {
this.form.spyLabel.BackColor = System.Drawing.Color.Red;
this.form.nameLabel.Text = "(none)";
this.form.addressLabel.Text = "(none)";
this.form.rssiLabel.Text = "(none)";
this.form.connectedLabel.Text = device.ConnectionStatus.ToString();
} else {
this.form.spyLabel.BackColor = System.Drawing.Color.Green;
this.form.nameLabel.Text = device.Name;
this.form.addressLabel.Text = device.BluetoothAddress.ToString();
this.form.rssiLabel.Text = eventArgs.RawSignalStrengthInDBm.ToString();
this.form.connectedLabel.Text = device.ConnectionStatus.ToString();
}
});
this.form.Invoke(update, device, eventArgs);
Console.Write(eventArgs.RawSignalStrengthInDBm + " ");
}
Any help is appreciated!