I managed to get connection with Azure IoTHub to send and receive device2Cloud Messages. I send the message with Microsoft.Azure.Devices.Client DeviceClient & consume the messages with Azure.Messaging.EventHubs.Consumer;
This is my code for sending the telemetry data
public async Task<IoTClientStatistics> SendDeviceToCloudMessageAsync(string messageContents)
{
if (_deviceClient == null || IsInitialized == false)
throw new InvalidAsynchronousStateException("You must call Initialize() before sending a message");
// IoT Hub message is a specific object that takes a byte array.
// You can then do many things...
Message message = new Message(Encoding.UTF8.GetBytes(messageContents));
// ...For example: add a custom application property to the message.
// An IoT hub can filter on these properties without access to the message body.
// Here we add the current runtime platform (Android, iOS, ...)
//message.Properties.Add("platform", Device.RuntimePlatform);
message.Properties.Add("name", "DeviceSample");
try
{
// Sending the event. If the event fails to be sent (no network, etc...)
// an exception will be raised.
await _deviceClient.SendEventAsync(message);
Console.WriteLine("Sent!");
// Updating statistics
_statistics.MessagesSent += 1;
_statistics.LastMessageSent = messageContents;
}
catch (Exception e)
{
_statistics.LastMessageSent = "Error: " + e;
_statistics.SendFailures += 1;
}
return _statistics;
}
This is my code for receiving the telemetry data
public async void ReceiveDeviceToCloudMessageAsync()
{
try
{
using CancellationTokenSource cancellationSource = new CancellationTokenSource();
cancellationSource.CancelAfter(TimeSpan.FromSeconds(45));
int eventsRead = 0;
int maximumEvents = 2;
var readEvents = _eventHubConsumerClient.ReadEventsAsync(cancellationSource.Token);
await foreach (PartitionEvent partitionEvent in readEvents)
{
string readFromPartition = partitionEvent.Partition.PartitionId;
byte[] eventBodyBytes = partitionEvent.Data.EventBody.ToArray();
yellowMessage($"\nMessage received on partition {partitionEvent.Partition.PartitionId}:");
string data = Encoding.UTF8.GetString(partitionEvent.Data.Body.ToArray());
greenMessage($"\tMessage body: {data}");
_statistics.MessagesReceived++;
_statistics.LastMessageReceived = data;
yellowMessage("\tApplication properties (set by device):");
foreach (KeyValuePair<string, object> prop in partitionEvent.Data.Properties)
{
PrintProperties(prop);
}
yellowMessage("\tSystem properties (set by IoT Hub):");
foreach (KeyValuePair<string, object> prop in partitionEvent.Data.SystemProperties)
{
PrintProperties(prop);
}
eventsRead++;
if (eventsRead >= maximumEvents)
{
break;
}
}
}
catch (TaskCanceledException e)
{
redMessage(e.ToString());
}
}
Problem is all the application properties I received at the partitionEvent DO NOT contain the application properties I added for the sent messages.
There is 1 possibility which still do not make sense to me, first my IoT device is configured to ignore message Application Properties. But I tested on another client app and the application properties were there normally (Using a Console App to send & a console app to receive with the same Key configuration)
Still struggle to understand why this is happening,
Is there any one expert on this topic ?
Related
I am new to Azure Service Bus and appreciate any help I can get.
In my current project, using c# and Azure.Messaging.ServiceBus we use an On-Premise server running a "Task Engine" windows service that listens to various queues (including MSMQ) to receive and process messages. We are migrating to Azure Service Bus Queue now.
I implemented ReceiveMessageAsync() method to read and process messages. The connection is persistent because the base class of the Task Engine service is already running in loop. While the below code works fine from my local pc (connected to VPN), it fails with the following error as soon as it's deployed to the on-premise server. The server also uses up all memory and shuts down causing other queues to terminate.
Error messages:
Azure.Messaging.ServiceBus.ServiceBusException: Creation of ReceivingAmqpLink did not complete in 30000 milliseconds. (ServiceTimeout)
Azure.Messaging.ServiceBus.ServiceBusException: 'receiver31' is closed (GeneralError)
Note:
Private Endpoint is enabled on Azure Service Bus and we use token and client credentials to connect to Azure.
All code below works fine locally when run for more than 2 hours and processes messages as soon as they are manually sent to queue using Azure Portal.
Code:
public **override **void StartUp(ContextBase context)
{
// Save the thread context
base.StartUp(context);
//Get values from Config
_tenantId = System.Configuration.ConfigurationManager.AppSettings["tenant-id"];
_clientId = System.Configuration.ConfigurationManager.AppSettings["client-id"];
_clientSecret = System.Configuration.ConfigurationManager.AppSettings["client-secret"];
_servicebusNamespace = System.Configuration.ConfigurationManager.AppSettings["servicebus-namespace"];
_messageQueueName = System.Configuration.ConfigurationManager.AppSettings["servicebus-inbound-queue"];
getAzureServiceBusAccess();
// Set the running flag
_isRunning = true;
}
//Called when service is initialized and then when Reset Connection happens due to error
private static void getAzureServiceBusAccess()
{
var _token = new ClientSecretCredential(_tenantId, _clientId, _clientSecret);
var clientOptions = new ServiceBusClientOptions()
{
TransportType = ServiceBusTransportType.AmqpWebSockets
};
_serviceBusClient = new ServiceBusClient(_servicebusNamespace, _token, clientOptions);
_serviceBusReceiver = _serviceBusClient.CreateReceiver(_messageQueueName, new ServiceBusReceiverOptions());
}
public **override **void DoAction()
{
// Make sure we haven't shut down
if (_isRunning)
{
// Wait next message
tryReceiveMessages();
}
}
private async void tryReceiveMessages()
{
try
{
ServiceBusReceivedMessage message = null;
message = await _serviceBusReceiver.ReceiveMessageAsync();
if (message != null && _isRunning)
{
try
{
string _messageBody = message.Body.ToString();
// <<Send message body to Task Adapter that adds it to the database and processes the job>>
await _serviceBusReceiver.CompleteMessageAsync(message);
}
catch (ServiceBusException s)
{
Tracer.RaiseError(Source.AzureSB, "Azure Service Bus Queue resulted in exception when processing message.", s);
throw;
}
catch (Exception ex)
{
Tracer.RaiseError(Source.AzureSB, "Unexpected error occurred moving task from Azure Service Bus to database; attempting to re-queue message.", ex);
if (message != null)
await _serviceBusReceiver.AbandonMessageAsync(message);
}
}
}
catch (ServiceBusException s)
{
tryResetConnections(s);
}
catch(Exception ex)
{
Tracer.RaiseError(Source.AzureSB, "Azure Service Bus Queue reset connection error.", ex);
throw;
}
}
private void tryResetConnections(Exception exception)
{
try
{
if (DateTime.Now.Subtract(LastQueueReset).TotalSeconds > 1800)
{
LastQueueReset = DateTime.Now;
getAzureServiceBusAccess();
}
else
{
//Send notification email to dev group
}
}
catch (Exception ex)
{
throw;
}
}
private async void closeAndDisposeConnectionAsync()
{
try
{
await _serviceBusReceiver.DisposeAsync();
}
catch(Exception ex)
{
//Do not throw and eat exception - Receiver may have been already disposed
}
try
{
await _serviceBusClient.DisposeAsync();
}
catch (Exception ex)
{
//Do not throw and eat exception - Client may have been already disposed
}
}
We tried to open the network settings on Azure Service Bus to public but that didn't resolve the issue.
I have requested the DevOps team to open ports 443, 5671 and 5672 for AMQPWebSockets and still waiting to hear back to test.
I have previously posted regarding the issue but haven't really found the problem.
Recently, I found this post regarding detachbuffer and wonder if this could be the reason i encounter the problem.
I have my UART for RS485 using a FTDI USB to 485 cable connected to Raspberry Pi on Windows IoT Core.
I set a dispatchertimer at every 1second to transmit polling to respective field devices.
I am able to tx and rx the 485 data with no problem.
However, after the polling loop to about 20 times it just crash and exited the debug mode.
I used try & catch to trap the exception but could not get it. However, i manage to read the error message at the debug output pane - The program [0xBB0] PwD.exe' has exited with code -1073741811 (0xc000000d).
I wonder if i repeatedly transmit polling, dataWriteObject.DetachBuffer(); would cause the problem?
Thanks.
snippets of my code are as follow;
private void PollTimer_Tick(object sender, object e)
{
if (pollCounter <= maxDevice)
{
var a = pollCounter | 0x80;
pollCounter++;
TxAdr = Convert.ToByte(a);
TxCmd = TxPoll;
TxPollCard();
}
else pollCounter = 0;
}
private async void TxPollCard()
{
if (serialPort != null)
{
List<byte> data = new List<byte>();
data.Add(TxHeader);
data.Add(TxAdr);
data.Add(TxCmd);
TxChkSum = 0;
foreach (byte a in data)
{
TxChkSum += a;
}
TxChkSum = (byte)(TxChkSum - 0x80);
data.Add(TxChkSum);
try
{
// Create the DataWriter object and attach to OutputStream
dataWriteObject = new DataWriter(serialPort.OutputStream);
dataWriteObject.WriteBytes(data.ToArray());
Task<UInt32> storeAsyncTask;
// Launch an async task to complete the write operation
storeAsyncTask = dataWriteObject.StoreAsync().AsTask();
UInt32 bytesWritten = await storeAsyncTask;
dataWriteObject.DetachBuffer();
}
catch (Exception ex)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
MainStatusDisplay.Text = ex.Message;
});
}
}
else
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
MainStatusDisplay.Text = "No UART port found";
});
}
}
Update:
Additional test i did which i disconnect the devices & keep transmitting without expecting response, it didn't crash.
On the other hand, i stop transmit and only listen to the 485 bus, it didn't crash either.
I am working on HoloLens (Unity-UWP) and trying to make a connection with PC (UWP) or Android phone work (Xamarin). So far I tried client and host with both Bluetooth and TCP (even two versions with different libraries) on Android and UWP. I kept the code entirely separated from user interface, so that it is easier to use, to understand and modular. An Action<string> is used to output results (error logs and sent messages).
Everything that is not on the HoloLens works fine (even though it's exactly the same code). It worked from PC (UWP) to Android with client and host switched. But it doesn't even work between HoloLens and PC (UWP). The behavior ranged from crashes (mostly for Bluetooth) to instant disconnection. The last tests resulted in disconnection once bytes are about to be received. It could even read the first 4 bytes (uint for the length of the following UTF-8 message), but then it was disconnected. The other devices seemed to work fine.
What I know: Capabilities are set, the code works, the issue is likely something that is common for everything that has to do with networking and HoloLens.
So the question is, is Unity or HoloLens incompatible with something I am using? What I used which is worth mentioning: StreamSocket, BinaryWriter, BinaryReader, Task (async, await). Or is HoloLens actively blocking communication with applications on other devices? I know it can connect to devices with Bluetooth and that it can connect via TCP, and it looks like people succeed to get it to work. Are there known issues? Or is there something with Unity that causes this - a bad setting maybe? Do I have to use async methods or only sync? Are there incompatibility issues with Tasks/Threads and Unity? Is this possibly the issue (inability to consent to permissions)?
Another thing to note is that I cannot ping HoloLens via its IP by using the cmd, even though the IP is correct.
I'd appreciate any advice, answer or guess. I can provide more information if requested (see also the comments below). I would suggest to focus on the TCP connection as it seemed to be working better and appears to be more "basic." Here is the code:
using System;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using Windows.Networking;
using Windows.Networking.Sockets;
#region Common
public abstract class TcpCore
{
protected StreamSocket Socket;
protected BinaryWriter BWriter;
protected BinaryReader BReader;
protected Task ReadingTask;
public bool DetailedInfos { get; set; } = false;
public bool Listening { get; protected set; }
public ActionSingle<string> MessageOutput { get; protected set; } = new ActionSingle<string> (); // Used for message and debug output. They wrap an Action and allow safer use.
public ActionSingle<string> LogOutput { get; protected set; } = new ActionSingle<string> ();
protected const string USED_PORT = "1337";
protected readonly Encoding USED_ENCODING = Encoding.UTF8;
public abstract void Disconnect ();
protected void StartCommunication ()
{
Stream streamOut = Socket.OutputStream.AsStreamForWrite ();
Stream streamIn = Socket.InputStream.AsStreamForRead ();
BWriter = new BinaryWriter (streamOut); //{ AutoFlush = true };
BReader = new BinaryReader (streamIn);
LogOutput.Trigger ("Connection established.");
ReadingTask = new Task (() => StartReading ());
ReadingTask.Start ();
}
public void SendMessage (string message)
{
// There's no need to send a zero length message.
if (string.IsNullOrEmpty (message)) return;
// Make sure that the connection is still up and there is a message to send.
if (Socket == null || BWriter == null) { LogOutput.Trigger ("Cannot send message: No clients connected."); return; }
uint length = (uint) message.Length;
byte[] countBuffer = BitConverter.GetBytes (length);
byte[] buffer = USED_ENCODING.GetBytes (message);
if (DetailedInfos) LogOutput.Trigger ("Sending: " + message);
BWriter.Write (countBuffer);
BWriter.Write (buffer);
BWriter.Flush ();
}
protected void StartReading ()
{
if (DetailedInfos) LogOutput.Trigger ("Starting to listen for input.");
Listening = true;
while (Listening)
{
try
{
if (DetailedInfos) LogOutput.Trigger ("Starting a listen iteration.");
// Based on the protocol we've defined, the first uint is the size of the message. [UInt (4)] + [Message (1*n)] - The UInt describes the length of the message (=n).
uint length = BReader.ReadUInt32 ();
if (DetailedInfos) LogOutput.Trigger ("ReadLength: " + length.ToString ());
MessageOutput.Trigger ("A");
byte[] messageBuffer = BReader.ReadBytes ((int) length);
MessageOutput.Trigger ("B");
string message = USED_ENCODING.GetString (messageBuffer);
MessageOutput.Trigger ("Received Message: " + message);
}
catch (Exception e)
{
// If this is an unknown status it means that the error is fatal and retry will likely fail.
if (SocketError.GetStatus (e.HResult) == SocketErrorStatus.Unknown)
{
// Seems to occur on disconnects. Let's not throw().
Listening = false;
Disconnect ();
LogOutput.Trigger ("Unknown error occurred: " + e.Message);
break;
}
else
{
Listening = false;
Disconnect ();
break;
}
}
}
LogOutput.Trigger ("Stopped to listen for input.");
}
}
#endregion
#region Client
public class GTcpClient : TcpCore
{
public async void Connect (string target, string port = USED_PORT) // Target is IP address.
{
try
{
Socket = new StreamSocket ();
HostName serverHost = new HostName (target);
await Socket.ConnectAsync (serverHost, port);
LogOutput.Trigger ("Connection successful to: " + target + ":" + port);
StartCommunication ();
}
catch (Exception e)
{
LogOutput.Trigger ("Connection error: " + e.Message);
}
}
public override void Disconnect ()
{
Listening = false;
if (BWriter != null) { BWriter.Dispose (); BWriter.Dispose (); BWriter = null; }
if (BReader != null) { BReader.Dispose (); BReader.Dispose (); BReader = null; }
if (Socket != null) { Socket.Dispose (); Socket = null; }
if (ReadingTask != null) { ReadingTask = null; }
}
}
#endregion
#region Server
public class GTcpServer : TcpCore
{
private StreamSocketListener socketListener;
public bool AutoResponse { get; set; } = false;
public async void StartServer ()
{
try
{
//Create a StreamSocketListener to start listening for TCP connections.
socketListener = new StreamSocketListener ();
//Hook up an event handler to call when connections are received.
socketListener.ConnectionReceived += ConnectionReceived;
//Start listening for incoming TCP connections on the specified port. You can specify any port that's not currently in use.
await socketListener.BindServiceNameAsync (USED_PORT);
}
catch (Exception e)
{
LogOutput.Trigger ("Connection error: " + e.Message);
}
}
private void ConnectionReceived (StreamSocketListener listener, StreamSocketListenerConnectionReceivedEventArgs args)
{
try
{
listener.Dispose ();
Socket = args.Socket;
if (DetailedInfos) LogOutput.Trigger ("Connection received from: " + Socket.Information.RemoteAddress + ":" + Socket.Information.RemotePort);
StartCommunication ();
}
catch (Exception e)
{
LogOutput.Trigger ("Connection Received error: " + e.Message);
}
}
public override void Disconnect ()
{
Listening = false;
if (socketListener != null) { socketListener.Dispose (); socketListener = null; }
if (BWriter != null) { BWriter.Dispose (); BWriter.Dispose (); BWriter = null; }
if (BReader != null) { BReader.Dispose (); BReader.Dispose (); BReader = null; }
if (Socket != null) { Socket.Dispose (); Socket = null; }
if (ReadingTask != null) { ReadingTask = null; }
}
}
#endregion
Coincidentially, I just implemented a BT connection between HoloLens and an UWP app. I followed the sample at https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/BluetoothRfcommChat.
As capabilities, I set "Bluetooth" (of course), "Internet (Client & Server)" and "Private Networks (Client & Server)". The steps on the server side then are:
Create an RfcommServiceProvider for your own or an existing (eg OBEX object push) service ID.
Create a StreamSocketListener and wire its ConnectionReceived Event.
Bind the service Name on the listener: listener.BindServiceNameAsync(provider.ServiceId.AsString(), SocketProtectionLevel.BluetoothEncryptionAllowNullAuthentication);
If you have a custom service ID, set its name along with other attributes you may want to configure. See the sample linked above for this. I think, this is mostly optional.
Start advertising the BT service: provider.StartAdvertising(listener, true);
Once a client connects, there is a StreamSocket in the StreamSocketListenerConnectionReceivedEventArgs that you can use to create a DataReader and DataWriter on like on any other stream. If you only want to allow one client, you can also stop advertising now.
On the client side, you would:
Show the DevicePicker and let the user select the peer device. Do not forget setting a filter like picker.Filter.SupportedDeviceSelectors.Add(BluetoothDevice.GetDeviceSelectorFromPairingState(true)); You can also allow unpaired devices, but you need to call PairAsync before you can continue in step 2. Also, I think there is no way to circumvent the user consent dialogue in this case, so I would recommend pairing before. To be honest, I did not check whether the unpaired stuff works on HoloLens.
You get a DeviceInformation instance from the picker, which you can use to obtain a BT device like await BluetoothDevice.FromIdAsync(info.Id);
Get the services from the device like device.GetRfcommServicesAsync(BluetoothCacheMode.Uncached); and select the one you are interested in. Note that I found that the built-in filtering did not behave as expected, so I just enumerated the result and compared the UUIDs manually. I believe that the UWP implementation performs a case-sensitive string comparison at some point, which might lead to the requested service not showing up although it is there.
Once you found your service, which I will call s from now on, create a StreamSocket and connect using socket.ConnectAsync(s.ConnectionHostName, s.ConnectionServiceName, SocketProtectionLevel.PlainSocket);
Again, you can not create the stream readers and writers like on the server side.
The answer is Threading.
For whoever may have similar issues, I found the solution. It was due to Unity itself, not HoloLens specifically. My issue was that I wrote my code separately in an own class instead of commingle it with UI code, which would have made it 1. unreadable and 2. not modular to use. So I tried a better coding approach (in my opinion). Everybody could download it and easily integrate it and have basic code for text messaging. While this was no issue for Xamarin and UWP, it was an issue for Unity itself (and there the Unity-UWP solution as well).
The receiving end of Bluetooth and TCP seemed to create an own thread (maybe even something else, but I didn't do it actively), which was unable to write on the main thread in Unity, which solely handles GameObjects (like the output log). Thus I got weird log outputs when I tested it on HoloLens.
I created a new TCP code which works for Unity but not the Unity-UWP solution, using TcpClient/TcpListener, in order to try another version with TCP connection. Luckily when I ran that in the editor it finally pointed on the issue itself: The main thread could not be accessed, which would have written into the UI - the text box for the log output. In order to solve that, I just had to use Unity's Update() method in order to set the text to the output. The variables themselves still could be accessed, but not the GameObjects.
I've got a WCF windows service running. It basically registers a number of clients and broadcasts messages to them as and when needed.
Generally it works fine, but recently I've been getting an error trying to register a new client, there are about 24 clients connected already, but when I try to register a 25th, I get "Index was outside the bounds of the array." being returned.
If I restart the service, all the clients reconnect, and the new client is able to register.
The NotifyServer method is called to broadcast a message to all the registered clients. This runs through the clients dictionary creating an async task that sends the message. This is done so that any issue happening with sending to one client does not impact the sending of the message to others.
The service is set up as to use a Reliable Connection.
// List of connected Clients
//
private static Dictionary<string, IBroadcastorCallBack> clients = new Dictionary<string, IBroadcastorCallBack>();
// lock indicator object
//
private static object locker = new object();
public string RegisterClient(string clientName)
{
string returnValue = "";
if (!string.IsNullOrEmpty(clientName))
{
try
{
var callback = OperationContext.Current.GetCallbackChannel<IBroadcastorCallBack>();
lock (locker)
{
// Remove the old client if its already there
//
if (clients.Keys.Contains(clientName))
{
clients.Remove(clientName);
}
// Add the new one
//
clients.Add(clientName, callback);
}
}
catch (Exception ex)
{
// Return any error message
//
returnValue = ex.Message;
}
}
return returnValue;
}
/// <summary>
/// Notify the service of a message that can be broadcast
/// </summary>
/// <param name="eventData">Message details</param>
///
public async void NotifyServer(EventDataType eventData)
{
DateTime Start = DateTime.Now;
// Get a list copy of the dictionary for all clients bar the one sending it. This is so we can't update the list inside the loop.
//
var clientlist = clients.Where(x => x.Key != eventData.ClientName).ToList();
// Logging
//
if (MetricBroadcast.Properties.Settings.Default.LogXML)
{
log.Debug("XML Broadcast from " + eventData.ClientName + " to " + clientlist.Count.ToString() + " clients:\n" + eventData.EventMessage + "\n");
}
// Broadcast to all the valid clients
//
var BroadcastToClientList = clientlist.Select(client => BroadcastMessage(client.Value, client.Key, eventData)).ToList();
// Wait until they are all done
//
await Task.WhenAll(BroadcastToClientList);
// If we are logging and the broadcast time is > 1 second, we make a log entry
//
DateTime End = DateTime.Now;
if (MetricBroadcast.Properties.Settings.Default.LogXML)
{
TimeSpan res = End - Start;
if (res.TotalSeconds > 1)
{
var timetaken = string.Format("XML Broadcast Time : {0,2:00}:{1,2:00}.{2,3:000}", res.Minutes, res.Seconds, res.Milliseconds);
log.Debug(timetaken);
}
}
}
private async Task<bool> BroadcastMessage(IBroadcastorCallBack clientCallback, string ClientKey, EventDataType eventData)
{
bool retval = true;
Exception savedEx = null;
DateTime BroadStart = DateTime.Now;
try
{
// Send the message to the current client
//
clientCallback.BroadcastToClient(eventData);
}
catch (Exception e)
{
// If we can't access the current clients callback method,
// we remove them from the clients list, as they've probably lost their connection.
//
clients.Remove(ClientKey);
savedEx = e;
retval = false;
}
// Log any broadcast that took > .5 seconds
//
DateTime BroadEnd = DateTime.Now;
if (MetricBroadcast.Properties.Settings.Default.LogXML)
{
TimeSpan res = BroadEnd - BroadStart;
if (res.TotalSeconds > .5)
{
var timetaken = string.Format("Single XML Broadcast Time to " + ClientKey + " : {0,2:00}:{1,2:00}.{2,3:000}", res.Minutes, res.Seconds, res.Milliseconds);
log.Debug(timetaken, savedEx);
}
}
return retval;
}
Most likely the problem is incorrect usage of static clients Dictionary in multithreaded environment. New client can register at any time, including in the middle of your BroadcastMessage and NotifyServer functions. Dictionary was not designed for access from multiple threads. Take for example this:
clients.Where(x => x.Key != eventData.ClientName).ToList();
What happens if client is removed in the middle of this enumeration? Who knows, because it was not designed for this, but most likely some exception (like your "index out of bounds") will be thrown. That is why you need to lock your dictionary for both writes and reads, not just for writes:
List<KeyValuePair<string, IBroadcastorCallback>> clientList;
lock (locker)
clientList = clients.Where(x => x.Key != eventData.ClientName).ToList();
Another option is to use ConcurrentDictionary class. It was designed for concurrent access and you can safely read and write to it from multiple threads.
I have a MQTT calls inside a loop and in each iteration, it should return a response from the subscriber so that I could use the value being forwarded after I published. But the problem is I don't know how would I do it.
I hope you have an idea there or maybe if I'm just not implementing it right, may you guide me through this. Thanks.
Here's my code:
// MyClientMgr
class MyClientMgr{
public long CurrentOutput { get; set; }
public void GetCurrentOutput(MyObjectParameters parameters, MqttClient client)
{
MyMessageObject msg = new MyMessageObject
{
Action = MyEnum.GetOutput,
Data = JsonConvert.SerializeObject(parameters)
}
mq_GetCurrentOutput(msg, client);
}
private void mq_GetCurrentOutput(MyMessageObject msg, MqttClient client)
{
string msgStr = JsonConvert.SerializeObject(msg);
client.Publish("getOutput", Encoding.UTF8.GetBytes(msgStr),
MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE, false);
client.MqttMsgPublishReceived += (sender, e) =>{
MyObjectOutput output = JsonConvert.DeserializeObject<MyObjectOutput>(Encoding.UTF8.GetString(e.Message));
CurrentOutput = output;
};
}
}
// MyServerMgr
class MyServerMgr
{
public void InitSubscriptions()
{
mq_GetOutput();
}
private void mq_GetOutput()
{
MqttClient clientSubscribe = new MqttClient(host);
string clientId = Guid.NewGuid().ToString();
clientSubscribe.Connect(clientId);
clientSubscribe.Subscribe(new string[] { "getOutput" }, new byte[] { MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE });
MqttClient clientPublish = new MqttClient(host);
string clientIdPub = Guid.NewGuid().ToString();
clientPublish.Connect(clientIdPub);
clientSubscribe.MqttMsgPublishReceived += (sender, e) => {
MyMessageObj msg = JsonConvert.DeserializeObject<MyMessageObj>(Encoding.UTF8.GetString(e.Message));
var output = msg.Output;
clientPublish.Publish("getOutput", Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(output)), MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE, false);
}
}
}
// MyCallerClass
class MyCallerClass
{
var host = "test.mqtt.org";
var myServer = new MyServerMgr(host);
var myClient = new MyClientMgr();
myServer.InitSubscriptions();
MqttClient client = new MqttClient(host);
for(int i = 0; i < 10; i++)
{
long output = 0;
MyObjectParameters parameters = {};
myClient.GetCurrentOutput(parameters, client) // here I call the method from my client manager
// to publish the getting of the output and assigned
// below for use, but the problem is the value doesn't
// being passed to the output variable because it is not
// yet returned by the server.
// Is there a way I could wait the process to
// get the response before assigning the output?
output = myClient.CurrentOutput; // output here will always be null
// because the response is not yet forwarded by the server
}
}
I have a loop in my caller class to call the mqtt publish for getting the output, but I have no idea how to get the output before it was assigned, I want to wait for the response first before going to the next.
I've already tried doing a while loop inside like this:
while(output == 0)
{
output = myClient.CurrentOutput;
}
Yes, I can get the output here, but it will slow down the process that much. And sometimes it will fail.
Please help me. Thanks.
It looks like you are trying to do synchronous communication over an asynchronous protocol (MQTT).
By this I mean you want to send a message and then wait for a response, this is not how MQTT works as there is no concept of a reply to a message at the protocol level.
I'm not that familiar with C# so I'll just give an abstract description of possible solution.
My suggestion would be to use a publishing thread, wait/pulse (Look at the Monitor class) to have this block after each publish and have the message handler call pulse when it has received the response.
If the response doesn't contain a wait to identify the original request you will also need a state machine variable to record which request is in progress.
You may want to look at putting a time out on the wait in case the other end does not respond for some reasons.
You can use AutoResetEvent class that has WaitOne() and Set() methods. Using WaitOne() after publish will wait until the message is published and using Set() under client_MqttMsgPublishReceived event will release the wait when the subscriber received the message he subscribed for.